cakelab
Home Projects Research Misc. Contact

JSON/POJO Codec

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

(free and open source)

Version 0.2.0

08-Dec-2016

download

program source

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 hierarchical set of name value pairs and a codec to map between JSON and sets of plain Java objects using the reflection API. Both methods will be explained in the following sections.

Mapping to Sets of Name-Value Pairs

Using this method, you get access to the JSON content via the classes JSONObject and JSONArray. The Parser creates a hierarchy of instances of these classes according to the given JSON document.

  • 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.

In both cases, values are decoded from the given string and stored in scalar types such as Integer, Double, Boolean etc.

Example

The following JSON document:

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

The given example above would result in an object hierarchy like this:

  • jsonObject
    • jsonArray
      • jsonObject
      • jsonObject

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

if ((boolean)json.get("member2")) {
	JSONArray array = (JSONArray)json.get("member3")
	JSONObject object = (JSONObject)array.get(1);
	object.set("m1", "You'r not my friend, dude!");
}

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

String jsonString = jsonObject.toString();

Mapping to Sets of Plain Java Objects

Using this method is based on the use of the JSONCodec in this package. It allows you to map JSON documents directly to Java objects and vice versa. This of course requires that the class of the object contains the member variables which exist in the JSON document.

Important Rules:

  • The data model has to be hierarchical.
  • static and transient members will be ignored.
  • Do not use enum (not supported yet).
  • 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).
More on those rules and workarounds see Rules and Workarounds section below.

Note: The codec internally uses the Parser class to turn the JSON document in instances of JSONObject and JSONArray before it maps it to your Java object. A better solution (possibly coming in future) is to parse it directly to the object.

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;
	/* hide this from the JSON codec */
	transient int dont_mind_me;
}
class B {
	String m1;
}

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

JSONCodec codec = new JSONCodec();
A obj = new A();
codec.decodeObject(jsonString, obj);

Access is much simpler this way, as the next code fragment demonstrates, which realises the same functionality as in the example above.

if (obj.member2) {
	obj.member3[1].m1 = "Now we'r talking!";
}

The codec is also used to turn any Java object into a JSON document.

String jsonString = codec.encodeObject(a);

Rules and Workarounds

Members with modifier static or transient

As a feature, static and transient members of an object will be ignored by the codec. The transient modifier allows you to control which members will be ignored by the codec which (for example) comes in handy if you have to resolve cyclic dependencies in your data model or you have runtime variables as members which you don't want to be persistent. The static members have been excluded because they are actually not a member of the object but of the class. That means, there is no point in storing them in every object.

Hierarchical Data Model

The codec traverses through your data model by iterating over all members of the given root object and following each reference to other objects until there are no more references. Thus, if there are for example two objects which reference each other, the codec would get stuck in a life lock forever following from one object to the other and back. To prevent such cycles in your data model you have to declare one of the references to be transient as shown in the example below.
class A {
	B refB;
}
class B {
	transient A refA;
}

Polymorphism and the Special Attribute class

For those who don't know: You apply polymorphism if you have multiple classes derived from a common super class or interface and you use the type of the super class or interface to reference the objects of the subclasses (see example below).
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;
}

JSON does not know anything about classes and is not type save. Thus, the parser requires additional information to determine the actual type of an object if it does not match with the type of the variable which will receive the reference.

Since Java has built-in runtime type information on each object stored in a global variable for each class which is called "class", this attribute name is implicitly reserved and can be used for this purpose if required.

For this purpose, you can configure the JSONCodec to use the class attribute. Refer to class JSONCodecConfiguration to do so.

When enabled, the codec will compare the type of a reference and the type of a referenced object and in case of differences (inheritance) it will automatically add an attribute class which holds the name of the class, when encoding. When decoding, the codec will in turn search for an attribute class and use the type information 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();
cfg.considerClassAttribute = 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 = (PolymorphismUser) 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());		

Enumaration Classes

enums require to be unique (of course). The codec does not know yet about enums, that's why it will literally store enums in the JSON document by their values and loads them as such, when decoding. As a result two enums with the same values are now different, because the test on equality compares their memory addresses and not their values. However, a future version of org.cakelab.json will consider this.

Nested Classes

Nested classes are those classes declared inside of another class. Nested classes have a pointer on their surrounding class, which usually has a pointer on the nested class (that's the reason why you declared this class initially inside of the other class). As a result your data model is no longer hierarchical and the codec will get stuck in an endless loop as explained above (see Hierachical Data Model). Another thing to be considered in a future version of org.cakelab.json.

Change log

  • [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