Gson Custom Seralizer for One Variable (Of Many) in an Object Using Typeadapter

Gson custom seralizer for one variable (of many) in an object using TypeAdapter

This is a great question because it isolates something that should be easy but actually requires a lot of code.

To start off, write an abstract TypeAdapterFactory that gives you hooks to modify the outgoing data. This example uses a new API in Gson 2.2 called getDelegateAdapter() that allows you to look up the adapter that Gson would use by default. The delegate adapters are extremely handy if you just want to tweak the standard behavior. And unlike full custom type adapters, they'll stay up-to-date automatically as you add and remove fields.

public abstract class CustomizedTypeAdapterFactory<C>
implements TypeAdapterFactory {
private final Class<C> customizedClass;

public CustomizedTypeAdapterFactory(Class<C> customizedClass) {
this.customizedClass = customizedClass;
}

@SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return type.getRawType() == customizedClass
? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
: null;
}

private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) {
final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<C>() {
@Override public void write(JsonWriter out, C value) throws IOException {
JsonElement tree = delegate.toJsonTree(value);
beforeWrite(value, tree);
elementAdapter.write(out, tree);
}
@Override public C read(JsonReader in) throws IOException {
JsonElement tree = elementAdapter.read(in);
afterRead(tree);
return delegate.fromJsonTree(tree);
}
};
}

/**
* Override this to muck with {@code toSerialize} before it is written to
* the outgoing JSON stream.
*/
protected void beforeWrite(C source, JsonElement toSerialize) {
}

/**
* Override this to muck with {@code deserialized} before it parsed into
* the application type.
*/
protected void afterRead(JsonElement deserialized) {
}
}

The above class uses the default serialization to get a JSON tree (represented by JsonElement), and then calls the hook method beforeWrite() to allow the subclass to customize that tree. Similarly for deserialization with afterRead().

Next we subclass this for the specific MyClass example. To illustrate I'll add a synthetic property called 'size' to the map when it's serialized. And for symmetry I'll remove it when it's deserialized. In practice this could be any customization.

private class MyClassTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyClass> {
private MyClassTypeAdapterFactory() {
super(MyClass.class);
}

@Override protected void beforeWrite(MyClass source, JsonElement toSerialize) {
JsonObject custom = toSerialize.getAsJsonObject().get("custom").getAsJsonObject();
custom.add("size", new JsonPrimitive(custom.entrySet().size()));
}

@Override protected void afterRead(JsonElement deserialized) {
JsonObject custom = deserialized.getAsJsonObject().get("custom").getAsJsonObject();
custom.remove("size");
}
}

Finally put it all together by creating a customized Gson instance that uses the new type adapter:

Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
.create();

Gson's new TypeAdapter and TypeAdapterFactory types are extremely powerful, but they're also abstract and take practice to use effectively. Hopefully you find this example useful!

GSON : Custom serialization for String

Try this

public class DataSerializer implements JsonSerializer<Data> {

@Override
public JsonElement serialize(Data data, Type typeOfSrc, JsonSerializationContext context) {

JsonObject object = new JsonObject();
object.addProperty("src", data.src);

return object;
}
}

Add this to Gson Like this

Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class, new DataSerializer())
.create();

Gson custom deseralizer for one variable in an object

You need to register a custom type adapter for the Apple type. In the type adapter, you will add logic to determine if you were given an array or a single object. Using that info, you can create your Apple object.

In adition to the below code, modify your Apple model object so that the seeds field isn't automatically parsed. Change the variable declaration to something like:

private List<Seed> seeds_funkyName;

Here is the code:

GsonBuilder b = new GsonBuilder();
b.registerTypeAdapter(Apple.class, new JsonDeserializer<Apple>() {
@Override
public Apple deserialize(JsonElement arg0, Type arg1,
JsonDeserializationContext arg2) throws JsonParseException {
JsonObject appleObj = arg0.getAsJsonObject();
Gson g = new Gson();
// Construct an apple (this shouldn't try to parse the seeds stuff
Apple a = g.fromJson(arg0, Apple.class);
List<Seed> seeds = null;
// Check to see if we were given a list or a single seed
if (appleObj.get("seeds").isJsonArray()) {
// if it's a list, just parse that from the JSON
seeds = g.fromJson(appleObj.get("seeds"),
new TypeToken<List<Seed>>() {
}.getType());
} else {
// otherwise, parse the single seed,
// and add it to the list
Seed single = g.fromJson(appleObj.get("seeds"), Seed.class);
seeds = new ArrayList<Seed>();
seeds.add(single);
}
// set the correct seed list
a.setSeeds(seeds);
return a;
}
});

For some more info, see the Gson guide.

adding an object using a custom typeadapter, jsonwriter in gson

In this case its better to use a JsonSerializer as opposed to a TypeAdapter, for the simple reason that serializers have access to their serialization context:

public class PairSerializer implements JsonSerializer<Pair> {

public PairSerializer() {
super();
}

@Override
public JsonElement serialize(final Pair value, final Type type,
final JsonSerializationContext context) {
final JsonObject jsonObj = new JsonObject();
jsonObj.add("first", context.serialize(value.getFirst()));
jsonObj.add("second", context.serialize(value.getSecond()));

return jsonObj;
}
}

The above sample code illustrates how to delegate serialization of target objects back to the main marshaller. The main advantage of this (apart from avoiding complicated workarounds) is that you can still take advantage of other type adaptors and custom serializers that might have been registered in the main context. Note that registration of serializers and adapters use the exact same code:

// With adapter
final Gson gson = new GsonBuilder().registerTypeAdapter(Pair.class,
new PairAdapter()).create();

// With serializer
final Gson gson = new GsonBuilder().registerTypeAdapter(Pair.class,
new PairSerializer()).create();

If you find that you need to stick with an adapter, then you can use an embedded Gson proxy to serialize the Pair properties for you, with the disadvantage that you lose access to custom registrations that you made on the parent Gson proxy:

public class PairAdapter extends TypeAdapter<Pair> {
final Gson embedded = new Gson();

public PairAdapter() {
super();
}

@Override
public void write(final JsonWriter out, final Pair value)
throws IOException {
out.beginObject();

out.name("first");
embedded.toJson(embedded.toJsonTree(value.getFirst()), out);

out.name("second");
embedded.toJson(embedded.toJsonTree(value.getSecond()), out);

out.endObject();
}

@Override
public Pair read(JsonReader in) throws IOException {
throw new UnsupportedOperationException();
}
}

gson nested custom serializer

Describe the generic relationships with TypeToken and use the context to transform the list:

TypeToken<List<User>> typeDescription = new TypeToken<List<User>>() {};
JsonElement members = context.serialize(src.getMembers(), typeDescription.getType());
object.add("members", members);

If User isn't a standard bean you may need a TypeAdapter for it too.

Gson Serializer for a given class if in another specific class

You can implement custom com.google.gson.JsonSerializer for a Date class and use com.google.gson.annotations.JsonAdapte annotation for given field to register it. See below example:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;

import java.lang.reflect.Type;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class GsonApp {

public static void main(String[] args) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();

System.out.println(gson.toJson(new DatesPojo(new Date())));
}

}

class CustomDateJsonSerializer implements JsonSerializer<Date> {
@Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
String format = src.toInstant().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_TIME);
return new JsonPrimitive(format + " ISO TIME");
}
}

class DatesPojo {

@JsonAdapter(CustomDateJsonSerializer.class)
@SerializedName("customDate")
private final Date mDate0;

@SerializedName("effectiveDate")
private final Date mDate1;

public DatesPojo(Date mDate) {
this.mDate0 = mDate;
this.mDate1 = mDate;
}

public Date getmDate0() {
return mDate0;
}

public Date getmDate1() {
return mDate1;
}
}

Above code prints:

{
"customDate": "22:37:21.806+01:00 ISO TIME",
"effectiveDate": "Jan 22, 2020 10:37:21 PM"
}

GSON Custom serialization with annotations

I found an answer myself. It turns out there already is an annotation for this kind of case in GSON. It's called @JsonAdapter.

First I had to create a TypeAdapterFactory:

public class BaseModelForeignKeyTypeAdapterFactory implements TypeAdapterFactory {

@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (!BaseModel.class.isAssignableFrom(type.getRawType())) {
return null;
}

TypeAdapter defaultAdapter = gson.getAdapter(type);

//noinspection unchecked
return (TypeAdapter<T>) new Adapter(defaultAdapter);
}

private static class Adapter<T extends BaseModel> extends TypeAdapter<T> {

private final TypeAdapter<T> defaultAdapter;

Adapter(TypeAdapter<T> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}

@Override
public void write(JsonWriter out, T value) throws IOException {
out.value(value.getId());
}

@Override
public T read(JsonReader in) throws IOException {
return defaultAdapter.read(in);
}
}
}

In the create() method I retrieve the default adapter Gson would use for this field and pass it to the Adapter for use when deserializing the field. This way this Adapter is only used for serialization, while deserialization is delegated to the default adapter.

Now I just need to annotate the fields in my Student class, which I want to be serialized as IDs with this TypeAdapterFactory like this:

public class Student extends BaseModel{
private int id;
private String name;

@JsonAdapter(BaseModelForeignKeyTypeAdapterFactory.class)
private Student goodFriend;

private Student bestFriend;
}

And this is all, now gson.toJson(student) will output:

{
id: 1,
name: "Jack",
goodFriend: 2, // using "ForeignKey" TypeAdapter
bestFriend: {id: 3, name "Tom"} // using default TypeAdapter
}

I hope this helps someone!

Add serialization method in custom class with GSON

I believe you should use TypeAdapterFactory.

Let your classes having custom serialization implement some interface, like:

interface CustomSerializable {
JsonObject toJson();
}

Then implement something like:

public class CustomTypeAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// if given type implements CustomSerializable return your TypeAdapter
// that is designed to use above mentioned interface
// so it uses method toJson() appropriately

// else return null for default serialization
return null;
}
}

Then it is just registering custom factory once, for example:

Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new CustomTypeAdapterFactory())
.create();

If you cannot change classes to implement CustomSerializable you can always also use reflection to determine inf there is a method toJson() but that might have nasty side effects if there is such and not added by you.

Gson custom type adapter to serialize null on one object type only?

If, by default, you don't want to serialize nulls, you can tell the JsonWriter to serialize it only if you are actually reading an Address instance.

Let's assume the following class:

class Address {
public String country = "UK";
public String city = "London";
}

Now we create a specific type adapter for the Address class. This is here where you explicitly say that even if the JsonWriter is not supposed to write null values in the response, you allow it to do so just for the Address field (see the comments in the code).

class AddressAdapter extends TypeAdapter<Address> {
@Override
public void write(JsonWriter out, Address address) throws IOException {
if (address == null) {
//if the writer was not allowed to write null values
//do it only for this field
if (!out.getSerializeNulls()) {
out.setSerializeNulls(true);
out.nullValue();
out.setSerializeNulls(false);
} else {
out.nullValue();
}
} else {
out.beginObject();
out.name("country");
out.value(address.country);
out.name("city");
out.value(address.city);
out.endObject();
}
}

@Override
public Address read(JsonReader in) throws IOException {
if(in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
Address address = new Address();
in.nextName();
address.country = in.nextString();
in.nextName();
address.city = in.nextString();
in.endObject();
return address;
}
}

Now you have to register this adapter so that the parser knows he has to use it when serializing/deserializing an Address field. To do that, use the annotation @JsonAdapter.

class Patient {
@JsonAdapter(AddressAdapter.class)
public Address address;
public String first_name;
public String last_name;
}

And it's done!

For instance let's take the patient in your example:

Patient patient = new Patient();
patient.last_name = "Doe";

With the parser set to serialize null values, you get:

{"address":null,"first_name":null,"last_name":"Doe"}

When you don't allow it (set by default):

{"address":null,"last_name":"Doe"}

by setting an address for the patient:

patient.address = new Address();
...
{"address":{"country":"UK","city":"London"},"last_name":"Doe"}

As a note if you want to stick with naming conventions on the Java side, you can use the annotation @SerializedName, for instance:

@SerializedName("first_name") public String firstName;
@SerializedName("last_name") public String lastName;

Hope it helps ! :-)



Related Topics



Leave a reply



Submit