cakelab
Home Projects Research Misc. Contact

JSON/POJO Codec

A Java JSON API which supports encoding and decoding of POJOs too.

(free and open source)

Version 1.0.0

24-Apr-2022

download

program source

github

Java Package Description

This package contains a JSON API and a JSON/POJO codec.

This package provides two ways to deal with JSON documents. A simple API mapping the JSON document to a JSON object tree in Java and a codec to map a JSON document to plain Java objects using the reflection API.

JSON Object Tree

JSON object tree is a representation of a JSON object in Java. Mapping between JSON objects and their Java representation is depicted in the following table:

JSON type Java Representation
object JSONObject
array JSONArray
true/false Boolean
number Double/Long
string String
null null
  • Instances of JSONObject contain name-value pairs while a value can reference other instances of JSONObject or JSONArray.
  • Instances of JSONArray contain a list of values while a value can be a reference to another JSONObject or JSONArray again.

JSON numbers are mapped to either Long or Double to keep accuracy high. JSONObject and JSONArray provide methods to retrieve either counterpart.

Example

The following JSON document:

{
  "member1": 1.0,
  "member2": true,
  "member3": [
    {
      "m1": "Hello friend!"
    },
    {
      "m2": null
    }
  ]
}
Can be parsed with an instance of the class Parser of this package:
JSONParser p = new DefaultParser();
JSONObject json = p.parse(jsonString);

Access to the member variables is provided via get and set methods.

if (json.getBoolean("member2")) {
	JSONArray array = json.getArray("member3")
	JSONObject object = array.getObject(1);
	object.set("m2", "Hello there!");
}

Both classes (JSONObject and JSONArray) provide a method toString to receive the corresponding JSON document with indenting.

String jsonString = jsonObject.toString();

A more compact representation can be achieved by a so-called JSONFormatter named JSONFormatterCompact.

JSONFormatter formatter = new JSONFormatterCompact();
String jsonString = jsonObject.toString(formatter);

Mapping to Java Objects

The JSONCodec allows to map JSON documents to Java objects and vice versa. This of course requires that the class of that object contains member variables corresponding to the entries found in a JSON object of a JSON document.

Rules:

  • The data model has to be hierarchical.
  • static and transient members will be ignored.
  • Do not use nested classes (not supported yet).
  • If you are using polymorphism on classes of member attributes you have to configure the codec specially (see Section on polymorphism below).
Read more about Rules and Workarounds below.

Example

To map the JSON document given above to Java objects you would need classes like this:
class A {
	double member1;
	boolean member2;
	B[] member3;
	
	transient int dont_mind_me; /* <-- hidden from encoding/decoding */
}
class B {
	String m1;
}

The JSONCodec is used to decode the given JSON document to Java objects.

A obj = jsonCodec.decodeObject(jsonString, A.class);

Or like this:

A obj = new A();
jsonCodec.decodeObject(jsonString, obj);

The codec is also used to turn Java objects into JSON documents.

String jsonString = codec.encodeObject(a);

Data Model Conversion

The JSONModeller converts between JSON object tree representation and a given Java object Data Model. This is useful, when assembling a JSON object tree in parts manually. The data model must conform to the same rules stated in section Mapping to Java Objects.

Example: Adding an object to given object tree root:

JSONObject root = ...;
MyObject myObject = ...;
JSONModeller modeller = new JSONModeller(cfg);
JSONObject jsonObject = modeller.toJSON(myObject);
root.put("myObject", jsonObject);

Example: Receiving an object from a given JSON object tree:

JSONObject root = ...;
JSONModeller modeller = new JSONModeller(cfg);
JSONObject jsonObject = root.get("myObject");
MyObject myObject = modeller.toJavaObject(jsonObject, MyObject.class);
A JSONFormatter and JSONParser can be used to transform between JSON document and JSON object tree representation to complete the cycle.

Rules and Workarounds

Modifiers static and transient

As a feature, static and transient members of an object will be ignored during the encoding/decoding process. This allows to control which data will be actually encoded.

Resolving Cyclic Dependencies

The codec traverses through your data model by iterating over all members of the given root object and following each reference to other objects. Thus, a data model must be strictly hierarchically (tree-like). Cyclic dependencies have to be resolved by the use of the transient modifier.

The following example demonstrates the use of transient to resolve a cyclic dependency between two classes A and B.

class A {
	B refB;
}
class B {
	transient A refA;
}

Polymorphism

Use of Polymorphism requires type information about objects referenced through a super class or interface. A JSONCodec must be configured to add this information (see JSONCodecConfiguration.supportClassAttribute).
class MySuperClass {}

class A extends MySuperClass {}

class B extends MySuperClass {}

/* class with a reference on any of those subclasses */

class PolymorphismUser {
	MySuperClass ref_on_A_or_B;
}

When support of the 'class' attribute is enabled, the codec will compare the type of a reference to the type of the referenced object. A class attribute will be added to the corresponding JSON object, if reference type and referenced object type differ. The class attribute will then carry the fully qualified name of the referenced object type. When decoding, the JSONCodec (or JSONModeller, see below) will in turn search for a class attribute in such cases, to instantiate the proper object.

/* given an object with reference on A, while reference is of type SuperClass */
PolymorphismUser object = new PolymorphismUser();
object.ref_on_A_or_B = new A();
	
/* configure and instantiate a codec which supports the "class" attribute. */
JSONCodecConfiguration cfg = new JSONCodecConfiguration()
									.supportClassAttribute(true);
JSONCodec codec = new JSONCodec(cfg);

/* encode object into a json string */
String json = codec.encodeObject(object);
	
/* and decode it again using a codec with the same configuration */
PolymorphismUser o2 = codec.decodeObject(json, PolymorphismUser.class);
	
/* member o2.ref_on_A_or_B is of type A again. */
assert object.ref_on_A_or_B.getClass().equals(o2.ref_on_A_or_B.getClass());		

Change log

  • [1.0.0] 2022-04-14
    • Separated codec from model handling.
  • [0.4.0] 2021-12-29
    • Major API and code cleanup.
    • Ensured thread-safety.
    • Added serialisation to output streams.
    • Removed deprecated methods.
    • Tests conform to JUnit 5.
  • [0.3.0] 2021-06-06
    • Introduced parser factory which allows to exchange parser implementation.
    • Added enum support
    • Removed deprecated methods.
    • Improved performance by reusing parser objects
  • [0.2.0](r1117)
    • Mod: refactored to cleanup package structure
    • Mod: separated config from string formatter
    • Add: introduced sorting of member variables in string formatter (if requested)
  • [0.1.9](r1040)
    • Add: Support of a special property named "class" to be interpreted as class type. Especially useful for member variables which reference derived classes of a common base class.
  • [0.1.8](r945)
    • Fix: incorrect utf8 escape sequences interpretation
    • Fix: Number format issue (caused by another application)
  • [0.1.6](r830)
    • add: support for explicit storing of non-UTF string values
  • [0.1.5](r725)
    • mod: object 2 json object now public
  • [0.1.4]
    • add: Support to ignore missing fields in target class
  • [0.1.3]
    • fix: ignored field was not ignored in decoding of json object
    • doc: Improved documentation on rules and workarounds

Holger Machens, 24-Apr-2022