Jackson: serialize non-initialized collection field as empty - java

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

Related

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

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

Nested custom deserialization in java with jackson

i've been wondering how to correctly solve this problem
I have a data model like this:
Class B
String fieldB1;
String fieldB2;
Class A
String fieldA1;
String fieldA2;
List<B> fieldA3;
(and then another third class which has the same hierarchy as the other with fields and a list of A objects, but for simplicity let's stick with A and B)
Now on the other side i have to deserialize these classes in classes with the same name and params just with different data types
So ^ must read as:
Class B
int fieldB1;
double fieldB2;
Class A
float fieldA1;
float fieldA2;
List<B> fieldA3;
Since i'm not experienced, my first guess was to write customs Deserializer in jackson for A and B, and when I deserialize a Class like B which does not have reference to other classes with custom deserialization methods, the conversion is easy.
But what about creating a custom deserializer for Class A? When I have to deserialize the fieldA3, aka the list of B objects, how should I operate? Should try to call in some way in the ClassACustomDeserializer the ClassBCustomDeserializer? How to do that?
Or is there another simpler solution to just tell jackson to transform some String fields in some other types based on my personal mapping?
This is how i would deserialize B
public class BDeserializer extends StdDeserializer<B> {
public BDeserializer() {
this(null);
}
public BDeserializer(Class<?> vc) {
super(vc);
}
#Override
public B deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int fieldB1= node.findValue("fieldB1").asInt();
double fieldB2= node.findValue("fieldB2").asDouble();
return new B(fieldB1,fieldB2);
}
}
Jackson is smart enough to convert the text value to an appropriate numeric type, so it should be able to deserialize a JSON like:
{ "fieldB1": 10, "fieldB2" : "0.333" }
to your
Class B
int fieldB1;
double fieldB2;
just nice, even w/o using a custom deserializer.
If you want to stick with a custom deserializer, for whatever reason,
you can either use JsonNode.traverse() to create a sub-parser:
JsonParser parser = node.findValue("fieldA3").traverse();
parser.setCodec(jp.getCodec());
List<B> list = parser.readValueAs(new TypeReference<List<B>>() {});
or navigate the token stream yourself, instead of using find:
while(jp.nextToken() != JsonToken.END_OBJECT) {
if(jp.currentToken() == JsonToken.FIELD_NAME) {
switch (jp.getCurrentName()) {
//...
case "fieldA3":
jp.nextToken();
list=jp.readValueAs(new TypeReference<List<ClassB>>() {}));
break;
}
}
}
the latter should be more efficient, if performance is of concern.

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.

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