2011-06-25

Gson v Jackson - Part 1

tl;dnr

Use Jackson, not Gson. Use this article as a reference for basic features.

What is this post?

Is this yet another JSON-to/from-Java API comparison? Yes, it is. It's the most comprehensive comparison of using Gson versus Jackson for common JSON-to/from-Java tasks known. The sections below walk through each section of the Gson user guide, demonstrating similar implementations with Jackson. API features beyond what is described in the Gson user guide are then reviewed.
Link To This ArticleQR Code Link To This Articlehttp://goo.gl/YuYhx

See part 6 of this series for a complete listing of and links to the various sections of this Gson user guide review.

Part 1 of this short series of articles ends with section 5.3 of the Gson user guide, titled "Nested Classes (including Inner Classes)". For cross reference, readers will likely find it useful to also have the Gson user guide open in another browser window. (An archive of the Gson user guide as of 2011.06.26 is available at https://sites.google.com/site/programmerbruce/downloads/Gson_User_Guide_2011.06.26.zip.)

This information is based on Gson release 1.7.1 and Jackson release 1.8.2.

The Gson User Guide Walk-through

The Gson user guide outlines many data-binding features. It does not cover in detail the streaming API, the tree model API, "manual" JSON parsing, or "manual" JSON generation, one token at a time.

The following sections demonstrate comparable Jackson-based solutions for the examples in the Gson user guide.

Using Gson


The basic component to map JSON to Java with Gson is named simply "Gson". Its creation and configuration is described in the user guide: "The primary class to use is Gson which you can just create by calling new Gson(). There is also a class GsonBuilder available that can be used to create a Gson instance with various settings..." The comparable constructs in Jackson are ObjectMapper and its configuration options.

The Gson Code:
Gson gson = new Gson();
// OR
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.[specify configs];
Gson gson = gsonBuilder.create();
Typical comparable Jackson Code:
ObjectMapper mapper = new ObjectMapper();
// OR
ObjectMapper mapper = new ObjectMapper();
mapper.[specify configs];
A key difference between the two approaches just demonstrated is that the Gson instance is immutable (and thus thread-safe in that different threads using the same instance cannot inadvertantly affect the results in other threads), but the ObjectMapper instance is not. To create immutable, thread-safe JSON readers and writers with Jackson, one can use the initial ObjectMapper like a builder, to specify the desired serialization/deserialization configurations, and then use it to generate an ObjectReader and/or an ObjectWriter. For example:
    ObjectMapper mapper = new ObjectMapper();
// configure ObjectMapper as desired

// To create thread-safe immutable JSON reader and writer:
ObjectReader reader = mapper.reader();
ObjectWriter writer = mapper.writer();
For convenience, most or all of the remaining examples in this series just use ObjectMapper to read and write JSON content.

Additional Comparison Notes: There are minor differences between these approaches and implementations. (For example, as implemented for Jackson, the ObjectWriter and ObjectReader instances can be used as base configurations to then generate new instances with new additional configurations applied. The current GsonBuilder/Gson API does not provide this same functionality.) Nothing significant to the Gson or Jackson end user.

Comparison Rating: COMPARABLE

Primitives Examples - Serialization


The Gson Code: See relevant section in the Gson user guide.

The comparable Jackson Code:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(1));
System.out.println(mapper.writeValueAsString("abcd"));
System.out.println(mapper.writeValueAsString(new Long(10)));
int[] values = {1};
System.out.println(mapper.writeValueAsString(values));
Comparison Notes: The results of the Jackson solution are identical with that of the Gson solution. Jackson makes it very easy to work with streams, writers, strings, and files. So, in some situations it requires somewhat more verbose coding. With this example, the difference to use a longer method name is insignificant.

Comparison Rating: COMPARABLE

Primitives Examples - Deserialization


The Gson Code: See relevant section in the Gson user guide.

The comparable Jackson Code:
ObjectMapper mapper = new ObjectMapper();
int one_i = mapper.readValue("1", int.class);
Integer one_I = mapper.readValue("1", Integer.class);
Long one_L = mapper.readValue("1", Long.class);
Boolean bool = mapper.readValue("false", Boolean.class);
String str = mapper.readValue("\"abc\"", String.class);

// throws JsonMappingException
String anotherStr =
mapper.readValue("[\"abc\"]", String.class);
Comparison Notes: With the exception of the last line, the results of the Jackson solution are identical with that of the Gson solution. Where the last line exposes a difference, is in that Gson will automagically transform a single-component JSON array with a primitive value into just the primitive value, unwrapping and discarding the array construct. That Gson has this ability and provides such a simple configuration option (by just specifying a primitive type) to use it can be useful. For Jackson, I logged issue 592 to request an enhancement for a similar auto-unwrapping feature. Otherwise, the current possible solutions with Jackson involve a few lines of "manual" JSON deserialization processing, or just using a collection type (such as an array) on the Java side, which both Gson and Jackson can very simply do.
// With Gson
String[] anotherStr =
gson.fromJson("[\"abc\"]", String[].class);

// With Jackson
String[] anotherStr =
mapper.readValue("[\"abc\"]", String[].class);
Comparison Ratings:
  • COMPARABLE for primitive value handling
  • +1 Gson for simple unwrapping single-component arrays

Exception Handling


Worth noting with the examples so far and many of the following examples is that Jackson requires explicit handling of the possible checked JsonMappingException and JsonGenerationException exceptions (either by throwing or catching them), but Gson does not have any such required checked exception handling. Instead, Gson will just throw an unchecked JsonParseException, which might catch a naive programmer by surprise, causing unexpected runtime failures (which can lead to other problems).

Gson also hides possible IOExceptions by wrapping them in unchecked exceptions, sometimes as JsonIOException and sometimes simply as RuntimeException.
// From Gson.toJson(JsonElement, Appendable):
...
catch (IOException e) {
throw new RuntimeException(e);
}

// From Gson.toJson(JsonElement, JsonWriter)
...
catch (IOException e) {
throw new JsonIOException(e);
}
Comparison Rating: +1 Jackson* for using checked exceptions for errors during JSON parsing and generation

*Yes, I am expressing a programming style preference. Given all of the Checked Exceptions Are Of Dubious Value arguments, I acknowledge no significant benefit in not requiring the programmer to be aware of and to handle the possibility of unexpected JSON input and/or incorrect data-binding configuration. As already expressed, I see this as possibly detrimental.

Object Examples


Gson makes it very convenient to bind a Java object field with a JSON element, since it will use any field by default, even if it is private. By default, Jackson requires something more than just the existence of a field, in order to bind a JSON element to it. It requires the field to be public, or to have getter/setter methods. Alternatively, the access visibility rules the ObjectMapper uses can be altered to allow private field use, or the class defining the field can be so annotated. Following are examples of all four Jackson-based solutions.

The Gson Code: See relevant section in the Gson user guide.

The comparable Jackson Code:
// with public fields
class BagOfPrimitives
{
public int value1 = 1;
public String value2 = "abc";
public transient int value3 = 3;
}
// ...
BagOfPrimitives obj = new BagOfPrimitives();
ObjectMapper mapper = new ObjectMapper();

// Serialization
String json = mapper.writeValueAsString(obj);
System.out.println(json);

// Deserialization
BagOfPrimitives obj2 =
mapper.readValue(json, BagOfPrimitives.class);
System.out.println(mapper.writeValueAsString(obj2));
// with getters/setters
class BagOfPrimitives
{
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;

public void setValue1(int v) {value1 = v;}
public int getValue1() {return value1;}
public void setValue2(String v) {value2 = v;}
public String getValue2() {return value2;}
}
// ...
BagOfPrimitives obj = new BagOfPrimitives();
ObjectMapper mapper = new ObjectMapper();

// Serialization
String json = mapper.writeValueAsString(obj);
System.out.println(json);

// Deserialization
BagOfPrimitives obj2 =
mapper.readValue(json, BagOfPrimitives.class);
System.out.println(mapper.writeValueAsString(obj2));
// with ObjectMapper's field visibility access rule modified
class BagOfPrimitives
{
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
}
// ...
BagOfPrimitives obj = new BagOfPrimitives();
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(
mapper.getVisibilityChecker()
.withFieldVisibility(Visibility.ANY));

// Serialization
String json = mapper.writeValueAsString(obj);
System.out.println(json);

// Deserialization
BagOfPrimitives obj2 =
mapper.readValue(json, BagOfPrimitives.class);
System.out.println(mapper.writeValueAsString(obj2));
// with the @JsonAutoDetect annotation
@JsonAutoDetect(fieldVisibility=Visibility.ANY)
class BagOfPrimitives
{
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
}
// ...
BagOfPrimitives obj = new BagOfPrimitives();
ObjectMapper mapper = new ObjectMapper();

// Serialization
String json = mapper.writeValueAsString(obj);
System.out.println(json);

// Deserialization
BagOfPrimitives obj2 =
mapper.readValue(json, BagOfPrimitives.class);
System.out.println(mapper.writeValueAsString(obj2));
Additional Code Notes:
  • Yet another approach available with Jackson to solve this problem is with Mix-In Annotations. Use of mix-in annotations is an option for all Jackson annotations.
  • To enhance Jackson to provide a more terse API for configuring member visibility rules, I logged issue 595. Please don't hesitate to vote for its implementation.
  • Gson cannot be configured to use getters/setters instead of direct field access, and the latest words on the subject are "[t]he prospects of such a feature making [it] into Gson are fairly low..."
Additional Comparison Notes on Default Behaviors: Both Gson and Jackson skip transient fields.

Before going further through the Gson user guide, comparing Gson use with Jackson, I want to point out a significant difference between these two libraries:

Jackson can deserialize any valid JSON object into a Map in one simple line of code. Gson cannot. And the Map generated by Jackson is composed of type-appropriate, standard Java library value objects.

Jackson can similarly simply turn any JSON array into a List or Object[], composed of type-appropriate, standard Java library value objects. Gson cannot.

So, given any JSON object, for folks that don't want to go through the details of designing a strongly typed Java data structure to bind the JSON data to, and ensuring that all of the data-binding is occurring correctly, they can just do this.
Map map = mapper.readValue(json, Map.class);
The following code demonstrates this feature and outlines what the deserialized Map is composed of.
// input: {"a":"A","b":{"c":"C","d":["D","E","F"]}}
String json = "{\"a\":\"A\",\"b\":{\"c\":\"C\",\"d\":" +
"[\"D\",\"E\",\"F\"]}}";

ObjectMapper mapper = new ObjectMapper();
Object foo = mapper.readValue(json, Object.class);
System.out.println(foo);
// output: {a=A, b={c=C, d=[D, E, F]}}
System.out.println(mapper.writeValueAsString(foo));
// output: {"a":"A","b":{"c":"C","d":["D","E","F"]}}
System.out.println(foo.getClass());
// output: class java.util.LinkedHashMap

// What specifically is in the fooMap?
Map<String, Object> fooMap = (Map) foo;
for (Entry entry : fooMap.entrySet())
{
System.out.printf("key: %s, value: %s (%s)\n",
entry.getKey(), entry.getValue(),
entry.getValue().getClass());
}
// output:
// key: a, value: A (class java.lang.String)
// key: b,
// value: {c=C, d=[D, E, F]} (class java.util.LinkedHashMap)

// And what is in the b map?
Map<String, Object> bMap = (Map) fooMap.get("b");
System.out.println(bMap);
// output: {c=C, d=[D, E, F]}
for (Entry entry: bMap.entrySet())
{
System.out.printf("key: %s, value: %s (%s)\n",
entry.getKey(), entry.getValue(),
entry.getValue().getClass());
}
// output:
// key: c, value: C (class java.lang.String)
// key: d, value: [D, E, F] (class java.util.ArrayList)

// Trying this with Gson...
Gson gson = new Gson();

gson.fromJson(json, Object.class);
// throws JsonParseException: Type information is unavailable

gson.fromJson(json, Map.class);
// throws JsonParseException: The JsonDeserializer
// MapTypeAdapter failed to deserialize json

Type mapOfStringObject =
new TypeToken<Map<String, Object>>() {}.getType();
gson.fromJson(json, mapOfStringObject);
// throws JsonParseException: Type information is unavailable
The comparable Gson Code to turn any JSON object into a Map:
// Gson's closest equivalent ability
JsonElement je = new JsonParser().parse(json);
JsonObject jo = je.getAsJsonObject();
System.out.println(jo);
// output: {"a":"A","b":{"c":"C","d":["D","E","F"]}}

// The JsonObject can be used similarly as a Map is used, but
// it's not a Map, and it is composed of Gson API components.
// To turn it into a real Map, without Gson components...
Map<String, Object> map = createMapFromJsonObject(jo);
System.out.println(map);
// output: {b={d=[D, E, F], c=C}, a=A}

// ...

static Map<String, Object> createMapFromJsonObject(
JsonObject jo)
{
Map<String, Object> map = new HashMap<String, Object>();
for (Entry<String, JsonElement> entry : jo.entrySet())
{
String key = entry.getKey();
JsonElement value = entry.getValue();
map.put(key, getValueFromJsonElement(value));
}
return map;
}

static Object getValueFromJsonElement(JsonElement je)
{
if (je.isJsonObject())
{
return createMapFromJsonObject(je.getAsJsonObject());
}
else if (je.isJsonArray())
{
JsonArray array = je.getAsJsonArray();
List<Object> list = new ArrayList<Object>(array.size());
for (JsonElement element : array)
{
list.add(getValueFromJsonElement(element));
}
return list;
}
else if (je.isJsonNull())
{
return null;
}
else // must be primitive
{
JsonPrimitive p = je.getAsJsonPrimitive();
if (p.isBoolean()) return p.getAsBoolean();
if (p.isString()) return p.getAsString();
// else p is number, but don't know what kind
String s = p.getAsString();
try
{
return new BigInteger(s);
}
catch (NumberFormatException e)
{
// must be a decimal
return new BigDecimal(s);
}
}
}
With Gson, similar processing using custom deserializers is possible.

Comparison Rating: +1 Jackson for very simple deserialization of any JSON object to a Map, and any JSON array to a List, composed of type-appropriate, standard Java library value objects

Continuing with the Gson user guide...

Handling of null reference fields and null JSON values sometimes differs and is sometimes the same between the two APIs.

By default, during serialization, Gson skips null reference fields, and they do not appear in the JSON. Jackson includes them.
class BagOfPrimitives
{
public int value1 = 1;
public String value2 = null;
public transient int value3 = 3;
}

public class NullHandling
{
public static void main(String[] args) throws Exception
{
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
System.out.println(gson.toJson(obj));
// output: {"value1":1}

ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(obj));
// output: {"value1":1,"value2":null}
}
}
During deserialization, both libraries use a JSON element null value to set the associated Java field accordingly, as a null reference.
class BagOfPrimitives
{
public int value1 = 1;
public String value2 = "abc";
public transient int value3 = 3;
}

public class NullHandling
{
public static void main(String[] args) throws Exception
{
// input: {"value1":100,"value2":null}
String jsonInput = "{\"value1\":100,\"value2\":null}";

Gson gson = new Gson();
BagOfPrimitives obj1 =
gson.fromJson(jsonInput, BagOfPrimitives.class);
System.out.println(gson.toJson(obj1));
// output: {"value1":100}

ObjectMapper mapper = new ObjectMapper();
BagOfPrimitives obj2 =
mapper.readValue(jsonInput, BagOfPrimitives.class);
System.out.println(mapper.writeValueAsString(obj2));
// output: {"value1":100,"value2":null}
}
}
During deserialization, if the JSON does not contain an element to match a field defined in the Java class, both Gson and Jackson do not set the relevant reference field to null, or any relevant primitive field to its default value. To clarify, if the field is assigned a value during normal object creation, the value is not replaced with anything by Gson or Jackson during deserialization. (This somewhat contradicts the statement in the Gson user guide that, "While deserialization, a missing entry in JSON results in setting the corresponding field in the object to null.")
class BagOfPrimitives
{
public int value1 = 1;
public String value2 = "abc";
public transient int value3 = 3;
}

public class NullHandling
{
public static void main(String[] args) throws Exception
{
// input: {"value1":100}
String jsonInput = "{\"value1\":100}";

Gson gson = new Gson();
BagOfPrimitives obj1 =
gson.fromJson(jsonInput, BagOfPrimitives.class);
System.out.println(gson.toJson(obj1));
// output: {"value1":100,"value2":"abc"}

ObjectMapper mapper = new ObjectMapper();
BagOfPrimitives obj2 =
mapper.readValue(jsonInput, BagOfPrimitives.class);
System.out.println(mapper.writeValueAsString(obj2));
// output: {"value1":100,"value2":"abc"}
}
}
Additional information on null handling is covered later, in the "Null Object Support" section of the Gson user guide, reviewed in part 4 of this series.

Both libraries skip synthetic fields.
class BagOfPrimitives
{
public int value1 = 1;
public String value2 = "abc";
public transient int value3 = 3;

public class ThisCausesSyntheticFieldCreation{}
}

public class SyntheticFieldHandling
{
public static void main(String[] args) throws Exception
{
Gson gson = new Gson();
BagOfPrimitives obj = new BagOfPrimitives();
System.out.println(gson.toJson(obj));
// output: {"value1":100,"value2":"abc"}

ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(obj));
// output: {"value1":100,"value2":"abc"}
}
}
Comparison Ratings:
  • COMPARABLE** for direct field access handling
  • +1 Jackson for providing optional use of getters and setters
  • COMPARABLE for default handling of null
  • COMPARABLE for synthetic field handling
**That Jackson requires an explicit configuration to enable private field access is inconsequential, since the configuration options are very simple.

Object Examples - Missing Element Handling


Gson and Jackson both deserialize without problems when the JSON does not have an element to populate a Java field. They also do not erase whatever default value is otherwise provided for the field.
public class MissingElementHandling
{
public static void main(String[] args) throws Exception
{
// input: {"name":"Spike"}
String jsonInput = "{\"name\":\"Spike\"}";

Gson gson = new Gson();
Dog dog1 = gson.fromJson(jsonInput, Dog.class);
System.out.println(gson.toJson(dog1));
// output: {"name":"Spike","age":-1}

ObjectMapper mapper = new ObjectMapper();
Dog dog2 = mapper.readValue(jsonInput, Dog.class);
System.out.println(mapper.writeValueAsString(dog2));
// output: {"name":"Spike","age":-1}
}
}

class Dog
{
public String name = "UNNAMED";
public int age = -1;
}
Comparison Rating: COMPARABLE

Object Examples - Extra Element Handling


By default, Gson just ignores extra JSON elements that do not have matching Java fields. Jackson instead throws an UnrecognizedPropertyException, but offers two simple configuration options to enable extra element handling: the class can be annotated with @JsonIgnoreProperties(ignoreUnknown=true), or the ObjectMapper can be directly configured to not FAIL_ON_UNKNOWN_PROPERTIES. (Jackson also offers other ways to handle extra JSON elements. See http://wiki.fasterxml.com/JacksonHowToIgnoreUnknown for details.)
public class ExtraElementHandling
{
public static void main(String[] args) throws Exception
{
// input: {"name":"Spike","breed":"Collie"}
String json = "{\"name\":\"Spike\",\"breed\":\"Collie\"}";

Gson gson = new Gson();
Dog dog1 = gson.fromJson(json, Dog.class);
System.out.println(gson.toJson(dog1));
// output: {"name":"Spike"}

ObjectMapper mapper = new ObjectMapper();
mapper.configure(
DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,
false);

Dog dog2 = mapper.readValue(json, Dog.class);
System.out.println(mapper.writeValueAsString(dog2));
// output: {"name":"Spike"}
}
}

// alternative to configuring ObjectMapper
// @JsonIgnoreProperties(ignoreUnknown=true)
class Dog
{
public String name;
}
Comparison Rating: COMPARABLE***

***That Jackson requires an explicit configuration to enable skipping extra JSON elements is inconsequential, since the configuration options are very simple.

Static Nested Classes


Both Gson and Jackson serialize and deserialize instances of static nested classes very simply.
class A
{
public static class B {public String b;}
}

public class StaticNestedClassHandling
{
public static void main(String[] args) throws Exception
{
// input: {"b":"123"}
String jsonInput = "{\"b\":\"123\"}";

Gson gson = new Gson();
A.B b1 = gson.fromJson(jsonInput, A.B.class);
System.out.println(gson.toJson(b1));
// output: {"b":"123"}

ObjectMapper mapper = new ObjectMapper();
A.B b2 = mapper.readValue(jsonInput, A.B.class);
System.out.println(mapper.writeValueAsString(b2));
// output: {"b":"123"}
}
}
Comparison Rating: COMPARABLE

(Non-Static) Inner Classes - Serialization


Gson and Jackson serialize instances of inner classes similarly.
class A
{
public String a = "AAA";
public B ab = new B();
public class B {public String b = "BBB";}
}

public class InnerClassSerialization
{
public static void main(String[] args) throws Exception
{
A a = new A();
A.B ab = a.new B();
Gson gson = new Gson();
System.out.println(gson.toJson(a));
// output: {"a":"AAA","ab":{"b":"BBB"}}
System.out.println(gson.toJson(ab));
// output: {"b":"BBB"}

ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(a));
// output: {"a":"AAA","ab":{"b":"BBB"}}
System.out.println(mapper.writeValueAsString(ab));
// output: {"b":"BBB"}
}
}
Comparison Rating: COMPARABLE

Anonymous Inner Classes - Serialization


Anonymous inner class serialization ability differs between Gson and Jackson. Both libraries serialize an object with a reference to an anonymous inner class, including the values of all of the attributes, but only Jackson serializes the values given a direct reference to an anonymous inner class.
class A
{
public String a = "AAA";
public B ab = new B() {public String b2 = "123";};
}

class B
{
public String b = "BBB";
}

public class AnonymousInnerClassSerialization
{
public static void main(String[] args) throws Exception
{
A a = new A();

Gson gson = new Gson();
System.out.println(gson.toJson(a));
// output: {"a":"AAA","ab":{"b2":"123","b":"BBB"}}

System.out.println(gson.toJson(a.ab));
// output: EMPTY STRING

ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(a));
// output: {"a":"AAA","ab":{"b":"BBB","b2":"123"}}

System.out.println(mapper.writeValueAsString(a.ab));
// output: {"b":"BBB","b2":"123"}
}
}
Comparison Rating: +1 Jackson for successfully serializing from an anonymous inner class reference

(Non-Static) Inner Classes - Deserialization


Contradicting the explanation in the Gson user guide that instances of inner class cannot be deserialized without a custom InstanceCreator, the latest release of Gson actually deserializes to inner classes just fine. Jackson fails to perform similar deserialization without custom handling.
class A
{
public String a = "AAA";
public B ab = new B();
public class B {public String b = "BBB";}
}

public class InnerClassDeserialization
{
public static void main(String[] args) throws Exception
{
// input: {"a":"AAA","ab":{"b":"BBB"}}
String jsonInputA =
"{\"a\":\"AAA\",\"ab\":{\"b\":\"BBB\"}}";

// input: {"b":"BBB"}
String jsonInputB = "{\"b\":\"BBB\"}";

Gson gson = new Gson();
A a1 = gson.fromJson(jsonInputA, A.class);
System.out.println(gson.toJson(a1));
// output: {"a":"AAA","ab":{"b":"BBB"}}
A.B b1 = gson.fromJson(jsonInputB, A.B.class);
System.out.println(gson.toJson(b1));
// output: {"b":"BBB"}

ObjectMapper mapper = new ObjectMapper();

// throws JsonMappingException
// A a2 = mapper.readValue(jsonInputA, A.class);

// throws JsonMappingException
// A.B b2 = mapper.readValue(jsonInputB, A.B.class);
}
}
A comparable (and fragile) Jackson Solution:
class A
{
public String a = "AAA";
public B ab = new B();
public class B {public String b = "BBB";}
}

public class InnerClassDeserialization
{
public static void main(String[] args) throws Exception
{
// input: {"a":"AAA","ab":{"b":"BBB"}}
String jsonInputA =
"{\"a\":\"AAA\",\"ab\":{\"b\":\"BBB\"}}";

// input: {"b":"BBB"}
String jsonInputB = "{\"b\":\"BBB\"}";

Gson gson = new Gson();
A a1 = gson.fromJson(jsonInputA, A.class);
System.out.println(gson.toJson(a1));
// output: {"a":"AAA","ab":{"b":"BBB"}}
A.B ab1 = gson.fromJson(jsonInputB, A.B.class);
System.out.println(gson.toJson(ab1));
// output: {"b":"BBB"}

ObjectMapper mapper = new ObjectMapper();

A a2 = readAValue(mapper, jsonInputA);
System.out.println(mapper.writeValueAsString(a2));
// output: {"a":"AAA","ab":{"b":"BBB"}}

A.B ab2 = readAbValue(mapper, jsonInputB);
System.out.println(mapper.writeValueAsString(ab2));
// output: {"b":"BBB"}
}

static A readAValue(ObjectMapper mapper, String json)
throws Exception
{
JsonNode node = mapper.readTree(json);
A a = new A();
a.a = node.get("a").getTextValue();
a.ab = a.new B();
a.ab.b = node.get("ab").get("b").getTextValue();
return a;
}

static A.B readAbValue(ObjectMapper mapper, String json)
throws Exception
{
JsonNode node = mapper.readTree(json);
A a = new A();
a.ab = a.new B();
a.ab.b = node.get("b").getTextValue();
return a.ab;
}
}
Additional Notes: To enhance Jackson to easily deserialize to inner class instances, I logged issue 594. Please don't hesitate to vote for its delivery.

Comparison Rating: +1 Gson for simple deserialization of inner classes (even if the docs say it cannot do it)

Anonymous Inner Classes - Deserialization


Neither library successfully deserializes to anonymous inner classes, without "manual" deserialization processing. Gson skips and clears any fields defined in the anonymous inner class, and Jackson throws an exception.
class A
{
public String a = "AAA";
public B ab = new B() {public String b2 = "123";};
}

class B
{
public String b = "BBB";
}

public class AnonymousInnerClassDeserialization
{
public static void main(String[] args) throws Exception
{
// {"a":"a value","ab":{"b":"b value","b2":"b2 value"}}
String json1 = "{\"a\":\"a value\",\"ab\":" +
"{\"b\":\"b value\",\"b2\":\"b2 value\"}}";

// {"b":"b value","b2":"b2 value"}
String json2 = "{\"b\":\"b value\",\"b2\":\"b2 value\"}";

Gson gson = new Gson();
ObjectMapper mapper = new ObjectMapper();

A a = gson.fromJson(json1, A.class);
System.out.println(gson.toJson(a));
// output: {"a":"a value","ab":{"b":"b value"}}
// b2 field value assignment skipped
// and cleared default "123" assignment

B b = gson.fromJson(json2, B.class);
System.out.println(mapper.writeValueAsString(b));
// using Jackson's ObjectMapper, since Gson does not
// serialize fields introduced in anonymous inner
// class definitions
// output: {"b":"b value"}
// b2 cleared and skipped as above

// A a2 = mapper.readValue(json1, A.class);
// throws Exception

// B b2 = mapper.readValue(json2, B.class);
// throws Exception
}
}
Comparison Rating: COMPARABLE -- They both do not succeed.

Continue to part 2...

References And Resources:

4 comments:

  1. Thanks, very clear comparison.

    It is important to note that though deserialization of inner classes works, the most important property of inner classes fails: It will not be created inside the outer object, but inside null, meaning that calling b.getParent in the following code will return null.

    This means inner classes are now just confusing versions of static nested classes.

    I have only tested this when letting Gson do the initialization of the inner object as well, it might work fine in the example in the article, but that is not an option when using lists of unknown length.

    class A
    {
    public String a = "AAA";
    public List<B> bs;
    public class B {
    public String b = "BBB";
    /** This will return null if the object was deserialized by Gson */
    public A getParent() {
    return A.this
    }
    }
    }

    ReplyDelete
  2. Making a Map from gson is so much easier then that mammoth code you have there

    Gson g = new Gson();
    Map result = g.fromJson("{"a":"b"}, HashMap.class);

    that's pretty much the same as Jackson

    and it does use sensible java classes (from my quick checking)

    ReplyDelete
    Replies
    1. With a release of Gson newer than was available when this article was written, this assertion may be correct. I haven't tested it out, yet.

      With previous releases of Gson, using HashMap.class (or any other Map implementation) as the deserialization type was problematic. In this article, I see that I maybe didn't expand on why this approach didn't work well with Gson, at the time.

      Delete
  3. Thanks Bruce, this is a great post as usual. Cheers!

    ReplyDelete