Can Gson's custom serializers and deserializers get the class/field? - java

I am considering switching over to Gson.
From my beginner's knowledge, I know of two ways to implement custom serializers and deserializers:
JsonSerializer and JsonDeserializer, and
TypeAdaptor.
Consider the following:
public class Pojo {
#JsonAdaptor(MyCustomAdaptor.class)
private Integer number;
}
class MyCustomAdaptor extends TypeAdaptor<Integer> {
#Override
public Integer read(JsonReader in) throws IOException {
// ...
}
public void write(JsonWriter writer, Integer value) throws IOException {
// ...
}
}
I noticed that TypeAdaptor does not expose the Field of number. Nor is this the case with JsonSerializer and JsonDeserializer. I understand that by the time these classes are used in the marshaling/unmarshalling lifecycle, there is no need to know this information.
For a moment, let's pretend that the Field was exposed in TypeAdaptors. The following is a basic example of what I would do:
public class Pojo {
#JsonAdaptor(MyCustomAdaptor.class)
#FloatSerialize
private Number number;
}
class MyCustomAdaptor extends TypeAdaptor<Number> {
#Override
public Number read(JsonReader in, Field field) throws IOException {
// Do something if #FloatSerialize is present.
}
public void write(JsonWriter writer, Number value, Field field) throws IOException {
// Do something if #FloatSerialize is present.
}
}
I know this would not make sense because #JsonAdaptor can be used on classes and fields.
Also, I know there are better ways to accomplish the above example; it is just an example and is meaningless. Basically, I want to use annotations, on a per-field basis, to tell custom serializers/deserializers how to process.
Is this possible with Gson? Can you create a custom serializer/deserializer that exposes the annotated class/field/etc?

#JsonAdaptor(MyCustomAdaptor.class)
private Integer number;
You've annotated an Integer field.
I noticed that TypeAdaptor does not expose the Field of number
I think there is something you misunderstand.
You provide the TypeAdaptor, you gives it the same type as your field:
class MyCustomAdaptor extends TypeAdaptor<Integer>
Then at serialization, you have the java objet you've annotated as an input to serialize to a string, then you have your field directly available as 'value' in a parameter:
public void write(JsonWriter writer, Integer value) throws IOException
At deserialization, you have a json string as input to deserialize to the object you've annotated. In the type adaptor this object is your field, that's why read method return an Integer.
Integer read(JsonReader in) throws IOException
It's up to you to return an Integer from the JsonReader.
You can use a Number instead of an Integer for your field, then you can have float,integer,short and more in your field.
For this you bring a NumberAdaptor:
class MyCustomAdaptor extends TypeAdaptor<Number> {
Number read(JsonReader in, Field field) throws IOException;
void write(JsonWriter writer, Number value) throws IOException;
}
If you really want to think about 'field', then provide an adaptor for the whole objet:
class Pojo Adaptor extends TypeAdaptor< Pojo > {
Pojo read(JsonReader in) throws IOException;
void write(JsonWriter writer, Pojo value) throws IOException;
}
Then you have access to the whole object and its fields in the adaptor.
Same things form JsonSerializer/JsonDeserialize.

With your additional info:
class Pojo Adaptor extends TypeAdaptor< Pojo > {
Pojo read(JsonReader in) throws IOException{
for(Field f: Pojo.class.getDeclaredFields()){
f.steAccessible();
for(Annotation a : f.getAnnotations()){
// Do what you want here withe the field and its annotation
}
}
void write(JsonWriter writer, Pojo value) throws IOException{
for(Field f: Pojo.class.getDeclaredFields()){
f.steAccessible();
for(Annotation a : f.getAnnotations()){
// Do what you want here with the field and its annotation
}
}
}
}
I've wrote this from my memory but you've got the idea

Related

How retrieve Class that annotated by Jackson #JsonDeserialize?

My task is deserialize json object to enum. See example at the end of the page.
But I can't define which enum create in method JsonEnumDeserializer.deserialize (Drive or some other).
I want just annotate my enum #JsonDeserialize(using = JsonEnumDeserializer.class) and it should deserialize all annotated enum by way I described.
1) For this I tried extend org.codehaus.jackson.map.deser.std.EnumDeserializer but it fails at runtime requiring no arg constructor or fails at compile time requiring override just parent constructor EnumDeserializer(EnumResolver<?> res).
I tried dig into EnumDeserializer how it retrieve Enum Class but it did not give me any result.
2) Other solution add field "type" to json, but it's bad solution.
In method JsonEnumDeserializer.deserialize I would like retrieve Class that annotated by #JsonDeserialize (it will solve problem). Could somebody help me?
{
"make":"Mazda",
"model":"6",
"drive":{
"id":"FWD",
"name":"Front wheel drive"
}
}
public class Car {
private String make;
private String model;
private Drive drive;
/* setters and getters */
}
#JsonDeserialize(using = JsonEnumDeserializer.class)
public enum Drive {
FWD, RWD, AWD
}
public class JsonEnumDeserializer extends JsonDeserializer<Enum> {
#Override
public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String enumName = jp.readValueAsTree().findValue("id").asText();
return ??? /* Knowing enumName how can I figure out which enum Class return? Drive enum or other enum? I want write universal deserializer for all enum */
}
}
One solution I found is change Jackson to Gson. Gson can say instance of which Class is expected to be returned. Argument Type type.
public class GsonEnumDeserializer implements JsonDeserializer<Enum> {
#Override
public Enum deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
try {
Class enumClass = Class.forName(type.getTypeName());
String enumName = jsonElement.getAsJsonObject().get("id").getAsString();
return (Enum) enumClass.getMethod("valueOf", String.class).invoke(null, enumName);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/* usage */
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(Enum.class, new GsonEnumDeserializer())
.create();
Car car = gson.fromJson(carJson, Car.class);
You don't need a custom JsonDeserializer at all in this case.
The drive field in Car is an enum, not an Object, so your json should be:
{
"make":"Mazda",
"model":"6",
"drive":"FWD"
}
Either your json text or you're Car class is wrong.

replace field name using #JsonTypeInfo

I would like to know if there is a way to replace the fieldname using #JsonTypeInfo
Here is what I want to achieve
class Tnode<T>{
#JsonTypeInfo(use=Id.NAME, include=As.WRAPPER_OBJECT, property="type")
T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
I get output as
{
"obj": {
"Foo": {
"name": "xyz"
}
}
}
The whole point is I do not want an extra layer "obj" as field name. I want the "Foo" to be one level above. In the code I am setting generic type to a concrete type. I want concrete class name to show up rather than having it wrapped.
I did try changing to include=As.PROPERTY but it will stil output as "obj".
I did solve using custom serializer. But I have to set every field.
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectField("somename", value);
jgen.writeEndObject();
}
}
But the problem is when ever I have to add a instance variable in Tnode class I have to add that code in the custom serializer. And I want to avoid that.
Any suggestions?
There is no way to do that. Name of the property to contain Object, wrapped in type information, has to be statically known (to locate logical property). It can not vary.

Jackson: serialize non-initialized collection field as empty

I have a POJO with a field or property, containing collection of objects, something like this:
public class Box {
public List<Items> items;
}
By default, value of items is null, and I do not want to initialize it with empty list.
Now, if I try to serialize it with Jackson, I get NullPointerException. Is there a simple way to make Jackson not break on such value and serialize it as an empty collection: [ ]?
Note. This class is just a simplified example. In reality, there are a hundred of classes and a number of fields with different names in each of them, which are occasionally set to null sometimes somewhere in the code, breaking serialization in runtime.
If you do not want to change the contract of your POJO class, think about the possibility to define custom Jackson serializer / deserializer which extend JsonSerializer<T> and JsonDeserializer<T> respectively. E.g.:
public class CountryDeserializer extends JsonDeserializer<CountryCode> {
#Override
public CountryCode deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
return CountryCode.getByCode(jp.getText());
}
}
and then
#JsonDeserialize(using=CountryDeserializer.class)
private CountryCode country;
You can check whether your field is null and act accordingly, in both directions (serialization / deserialization).
Have you considered making this class a JavaBean?
In that case, you would be able to give a default value in the getter:
public class Box {
private List<Items> items;
public List<Items> getItems() {
if(null == items) {
return Collections.emptyList();
}
return this.items;
}
//Setter here
}
This approach would prevent a lot of trouble related to Jackson's assumptions.
Update: Based on clarification... You could implement a custom serializer for the list type (and/or any other desired customization). Please note that :
public class ListSerializer extends JsonSerializer<List> {
#Override
public void serialize(List value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
if (null == value) {
provider.defaultSerializeValue(new ArrayList<Object>(), jgen);
} else {
provider.defaultSerializeValue(value, jgen);
}
}
}
//Then your code could set the serializer on the object mapper as follows:
objectMapper.addSerializer(List.class, new ListSerializer());
Repeat for all such customization.
Code was inspired by this article: http://www.baeldung.com/jackson-custom-serialization

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;
}

Serialize embedded pair (key/value) instead of object with GSON

I want to create a custom serializer in GSON that insert a key/value pair in an object, not a new object. For example, supose this:
class Car {
#Expose
String model;
#Expose
Manufacturer manufacturer;
}
class Manufacturer {
#Expose
String name;
#Expose
String from;
}
I want to get then a JSON like this:
"car":{
"model":"beatle",
"manufacturer":"volkswagen",
"country":"Germany"
}
But no matter how I code the serializer, it insists to create a manufacturer object inside "car"
"manufacturer":{
"name":"volkswagen",
"country":"Germany"
}
How can I fix that, to get only key/value pairs?
PS: I cannot make significative changes in classes, because they are mapping the DB. It is just a example to simulate my problem.
Use custom serializer should help.
private class ManufacturerSerializer implements JsonSerializer<Manufacturer> {
public JsonElement serialize(Manufacturer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getName()); // or src.name public.
}
}
Refer this: https://sites.google.com/site/gson/gson-user-guide/#TOC-Custom-Serialization-and-Deserialization
You should define you own serializer and deserializer for the Car class which will avoid just serializing composited classes in the trivial way but will render just what you need.
That's not hard at all, for example you will define
class CarSerializer implements JsonSerializer<Car> {
JsonElement serialize(Car src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject o = new JsonObject();
o.addProperty("manufacturer", src.manufacturer.name);
...
}
}
Remember that you need to register the serializer as explained in documentation.
I was not possible to make what I want with JsonSerializer. I could only make it works OK with the new API of GSON, since 2.1 (TypeAdapter class).
public class ManufacturerSerializer extends TypeAdapter<Manufacturer> {
#Override
public void write(JsonWriter out, Manufacturer m) throws IOException {
out.value(m.getName());
out.name("country");
out.value(m.getFrom());
}
/* I only need Serialization, dont use this */
#Override
public Manufacturer read(JsonReader in) throws IOException {
return null;
}
}

Categories