Writing a serializer for Gson using Java 8 - java

I have a own class called MyDate and want to write a serializer of it for Gson. This code works:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyDate.class, new JsonSerializer<MyDate>() {
#Override
public JsonElement serialize(MyDate date, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(date.toString());
}
});
However I want to use the power of Java 8 and therefore tried
builder.registerTypeAdapter(MyDate.class, (date, typeOfSrc, context) ->new JsonPrimitive(date.toString()));
But here eclipse tells me
The target type of this expression must be a functional interface
What's wrong about that Java 8 code?

In order to replace an anonymous class with a lambda, the parameter must be a Single Method Interface (SMI).
This is an interface with a single abstract method.
GsonBuilder.registerTypeAdaper takes an Object as the second argument.
You need to first assign your lambda then pass in the method:
final JsonSerializer<MyDate> serializer = (date, typeOfSrc, context) -> new JsonPrimitive(date.toString());
builder.registerTypeAdapter(MyDate.class, serializer);
This way you tell compiler which SMI you would like to implement.

Related

Gson deserialize abstract class in kotlin [duplicate]

I have a tree object in JSON format I'm trying to deserialize with Gson. Each node contains its child nodes as fields of object type Node. Node is an interface, which has several concrete class implementations. During the deserialization process, how can I communicate to Gson which concrete class to implement when deserializing the node, if I do not know a priori which type the node belongs to? Each Node has a member field specifying the type. Is there a way to access the field when the object is in serialized form, and somehow communicate the type to Gson?
Thanks!
I'd suggest adding a custom JsonDeserializer for Nodes:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Node.class, new NodeDeserializer())
.create();
You will be able to access the JsonElement representing the node in the deserializer's method, convert that to a JsonObject, and retrieve the field that specifies the type. You can then create an instance of the correct type of Node based on that.
You will need to register both JSONSerializer and JSONDeserializer. Also you can implement a generic adapter for all your interfaces in the following way:
During Serialization : Add a META-info of the actual impl class type.
During DeSerialization : Retrieve that meta info and call the JSONDeserailize of that class
Here is the implementation that I have used for myself and works fine.
public class PropertyBasedInterfaceMarshal implements
JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_META_KEY = "CLASS_META_KEY";
#Override
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_META_KEY).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Object object, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonElement jsonEle = jsonSerializationContext.serialize(object, object.getClass());
jsonEle.getAsJsonObject().addProperty(CLASS_META_KEY,
object.getClass().getCanonicalName());
return jsonEle;
}
}
Then you could register this adapter for all your interfaces as follows
Gson gson = new GsonBuilder()
.registerTypeAdapter(IInterfaceOne.class,
new PropertyBasedInterfaceMarshal())
.registerTypeAdapter(IInterfaceTwo.class,
new PropertyBasedInterfaceMarshal()).create();
As far as I can tell this doesn't work for non-collection types, or more specifically, situations where the concrete type is used to serialize, and the interface type is used to deserialize. That is, if you have a simple class implementing an interface and you serialize the concrete class, then specify the interface to deserialize, you'll end up in an unrecoverable situation.
In the above example the type adapter is registered against the interface, but when you serialize using the concrete class it will not be used, meaning the CLASS_META_KEY data will never be set.
If you specify the adapter as a hierarchical adapter (thereby telling gson to use it for all types in the hierarchy), you'll end up in an infinite loop as the serializer will just keep calling itself.
Anyone know how to serialize from a concrete implementation of an interface, then deserialize using only the interface and an InstanceCreator?
By default it seems that gson will create the concrete instance, but does not set it's fields.
Issue is logged here:
http://code.google.com/p/google-gson/issues/detail?id=411&q=interface
I want to correct the above a little
public class PropertyMarshallerAbstractTask implements JsonSerializer<Object>, JsonDeserializer<Object> {
private static final String CLASS_TYPE = "CLASS_TYPE";
#Override
public Object deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObj = jsonElement.getAsJsonObject();
String className = jsonObj.get(CLASS_TYPE).getAsString();
try {
Class<?> clz = Class.forName(className);
return jsonDeserializationContext.deserialize(jsonElement, clz);
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
#Override
public JsonElement serialize(Object object, Type type, JsonSerializationContext jsonSerializationContext) {
Gson gson = new Gson(); //without this line it will not work
gson.toJson(object, object.getClass()); //and this one
JsonElement jsonElement = gson.toJsonTree(object); //it needs to replace to another method...toJsonTree
jsonElement.getAsJsonObject().addProperty(CLASS_TYPE, object.getClass().getCanonicalName());
return jsonElement;
}
}
And then I use it:
Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractTask.class, new PropertyMarshallerOfAbstractTask())
.create();
And then I can parse List (where I keep some non-abstract classes, which inherited from Abstract Task) to Json;
And it works in the opposite direction
List<AbstractTask> abstractTasks = gson.fromJson(json, new TypeToken<List<AbstractTask>>(){}.getType());
You have to use TypeToken class from Google Gson.
You will need of course has a generic class T to make it works
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);

Gson does not correctly serialize LocalDate

I am writing an android application, where I want to serialize instances of this Anime.java class. Its superclass AnimeBase.java has a field called aired, which is of the type DateRange. This DateRange contains two fields:
public LocalDate from;
public LocalDate to;
The serialization is very straight-forward (using gson) like this:
final Gson gson = new Gson();
String data = gson.toJson(obj);
However, in my result, the from and to fields are always empty like here:
// ...
"trailer_url": "https://www.youtube.com/embed/SlNpRThS9t8?enablejsapi\u003d1\u0026wmode\u003dopaque\u0026autoplay\u003d1",
"aired": {
"from": {}
},
"episodes": 16,
// ...
Here, to was null, so it is missing (and that is okay).
Why is gson not serializing these two LocalDates? Does it have something to do with the DateRanges setter & getter (which are a bit unusual, taking a OffsetDateTime instead of a LocalDate)?
Since these classes stem from a 3rd-party library, is there a good way for me to handle this without duplicating all the model classes in my own application for serializing/deserializing them?
Take a look at https://github.com/gkopff/gson-javatime-serialisers
There are serializers for LocalDate objects.
If you choose to create your own serializer:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<LocalDate>(){}.getType(), new LocalDateConverter());
Gson gson = builder.create();
...
public class LocalDateConverter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(DateTimeFormatter.ISO_LOCAL_DATE.format(src));
}
public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return DateTimeFormatter.ISO_LOCAL_DATE.parse(json.getAsString(), LocalDate::from);
}
}
I could now find the source of this problem.
Starting in Android 9, Google added something called "Restrictions on non-SDK interfaces" where they restrict the access to not publicly documented SDK interfaces in the Android dalvik runtime.
Since Gson by default uses the ReflectiveTypeAdapterFactory which itself looks for serializable fields in the object to be serialized it heavily depends on Reflection.
Google has documented this behaviour, that the used function Class.getDeclaredFields() which is used by ReflectiveTypeAdapterFactory do return only public accessible Fields, or more concrete, only fields which are whitelisted by Google.
https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces#results-of-keeping-non-sdk
In the referenced documentation, Google explicitly states the java.time.LocalDate fields as greylisted:
Ljava/time/LocalDate;->day:S,greylist-max-o
I am not sure why this access is still working in release mode and the behaviour only is present when the build is debuggable but I suppose this is something which will be removed in future Android versions too.
Because of this we added our own backwards-compatible Serializer (which is similar to the one of #k1r0, but still works with previously serialized values):
class LocalDateJsonSerializer : JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
override fun serialize(src: LocalDate, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonObject().also {
it.addProperty("year", src.year)
it.addProperty("month", src.monthValue)
it.addProperty("day", src.dayOfMonth)
}
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): LocalDate {
val jsonObject = json.asJsonObject
return LocalDate.of(jsonObject["year"].asInt, jsonObject["month"].asInt, jsonObject["day"].asInt)
}
}

Gson custom serialization not working for java.lang.Object

I am writing a custom Gson serializer
public class DogSerializer implements JsonSerializer<Object> {
#Override
public JsonElement serialize(Object src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty("name", "sasha");
return obj;
}
I am also registering the serialzer like
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Object.class, new DogSerializer());
Now if I do System.out.println(gsonBuilder.create().toJson([My-Test-Class])) the serializer is never called. I want the serializer to be called for all fields when I pass My-test-Class. Object.class doesn't seem to work. What should I do?
What should I do?
Redesign your approach if possible and bind other types. You cannot override serialization strategies for java.lang.Object and com.google.gson.JsonElement (as of 2.8.1 and prior by design):
// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.FACTORY);

Is possible to use setters when Gson deserializes a JSON?

Is there any way the set methods of a given class, are used when using Gson's fromJson method?
I would like to do this because for every String global variable of the target class a trim is made.
Is there any GSON API annotation for this?
I am aware that GSON provides the ability to write custom serializers/deserializers but I would like to know if there is another way to achieve this.
No, there is not. Gson works mainly by reflection on instance fields. So if you do not plan to move to Jackson that has this feature I think you cannot have a general way to call your setters. So there's no annotation for that.
BUT
to achieve your specific need you could:
write your own custom TypeAdapter or
create a constructor that has the string you intend to trim and create a custom InstanceCreator or
parse your JSON as JsonObject, do some processing of the strings and then use that object as source for parsing into your class.
I can provide you with more hints as long as you post some code or give information about your data/JSON.
I implemented a JsonDeserializer<String> and registered it on GsonBuilder. So, to all String fields received, Gson will use my StringGsonTypeAdapter to deserialize the value.
Below is my code:
import static net.hugonardo.java.commons.text.StringUtils.normalizeSpace;
import static net.hugonardo.java.commons.text.StringUtils.trimToNull;
final class StringGsonTypeAdapter implements JsonDeserializer<String> {
private static final StringGsonTypeAdapter INSTANCE = new StringGsonTypeAdapter();
static StringGsonTypeAdapter instance() {
return INSTANCE;
}
#Override
public String deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return normalizeSpace(trimToNull(jsonElement.getAsString()));
}
}
...and my GsonBuilder:
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, StringGsonTypeAdapter.instance())
.create())

java jackson parse object containing a generic type object

i have the following problem.
I have to parse a json request into an object that contains a generic type field.
EDIT
i have made some tests using a regular class type (so i make it work before i replace it with generic). Now parsing for a single element works great.
The issue is when i need to parse out a list object out of that class.
So i have to inform jackson somehow that my T is of type list instead of just AlbumModel.
Here is what i have tried.
#Override
public ListResponseModel<AlbumModel> parse(String responseBody) throws Exception {
JavaType type = mapper.getTypeFactory().constructParametricType(ResponseModel.class,
AlbumModel.class);
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, type));
}
But the code above doesn't work. what is the solution for something like this?
my generic type in the ListResponseModel is defined like: List<T> data
succeeded like:
public class BaseResponseModel<T> {
#JsonProperty("data")
private T data;
#JsonProperty("paginations")
private PaginationModel pagination;
}
so far i have the following code but it always parses into a Hash.
public class ResponseParser extends BaseJacksonMapperResponseParser<ResponseModel<AlbumModel>> {
public static final String TAG = ResponseParser.class.getSimpleName();
#Override
public ResponseModel<AlbumModel> parse(String responseBody) throws Exception {
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, AlbumModel.class));
}
}
public abstract class BaseJacksonMapperResponseParser<T> implements HttpResponseParser<T> {
public static final String TAG = BaseJacksonMapperResponseParser.class.getSimpleName();
public static ObjectMapper mapper = new ObjectMapper();
static {
mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
}
}
I agree with eugen's answer but just wanted to expand on it a bit. The first step is to refactor your parse method so it takes a second argument. Instead of allocating the type reference in your method, you require the caller to pass in a TypeReference instance.
public BaseResponseModel<T> parse(String responseBody, TypeReference<T> ref) throws Exception {
return mapper.readValue(responseBody, ref);
}
Unfortunately your snippet does not show the code which calls parse - so I'll make something up:
BaseResponseParser<Collection<Person>> parser = new BaseResponseParser<Collection<Person>>();
BaseResponseModel<Collection<Person>> result = parser.parse(jsonText, new TypeReference<Collection<Person>>(){});
Notice that when the TypeReference instance is compiled in this case, it a type reference to the real concrete class that we expect.
You could do the same thing passing in a Class at runtime, however TypeReference is a bit more powerful because it even works when type T is a generic collection. There is some magic in the TypeReference implementation that allows it to hold onto type information that would normally be erased.
[update]
Updated to use Collection<Person>. Note - as far as I know as List<Whatever> should work also, but I double checked a project where I was using jackson to deserialize collections. Base class Collection definitely worked so I stayed with that.
Your type T will be "erased" at runtime, so Jackson does not know what is the real type of T and deserializes it to a Map. You need a second parameter to your parse method that will be Class<T> clazz or TypeReference<T> or java.lang.reflect.Type.
EDIT
Small explanation on the magic of TypeReference. When you do new XX() {} you are creating a anonymous class, so if it is a class with typevariables (parameterized if you prefer), new X<List<Y>>() {}, you will be able to retrieve List<Y> as a java Type at runtime. It is very similar as if you had done :
abstract class MyGenericClass<T> {}
class MySpecializedClass extends MyGenericClass<List<Y>> {}
Since you're using Jackson you probably need to create a custom JsonDeserializer or JsonSerializer depending on whether you're handing the response or request. I've done this with Dates because on my response I want a standard view. I'm not 100% positive it will work with a generic field though. Here is an example of what I'm doing:
public class DateSerializer extends JsonSerializer<Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ");
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String dateString = dateFormat.format(value);
jgen.writeString(dateString);
}
}
Then I just add it to my class like so:
#JsonSerialize(using = DateSerializer.class)
public Date getModifiedDate() {
return modifiedDate;
}

Categories