Shoot to kill

We found that we had accumulated four libraries for generating/parsing JSON:

  1. json-lib - net.sf.json.JSONObject etc
  2. json-lib - org.json.JSONObject etc
  3. GSON
  4. Jackson

In fact, the most-commonly-used library was the old json-lib, which includes some entertaining misfeatures. Consider adding a property to an object with a string value:

@Test public void put_adds_a_string_to_an_object() {
    JSONObject obj = new JSONObject();
    String value = "value";
    obj.put("prop", value);
    assertThat(obj.get("prop"), is(equalTo(value)));
    assertThat(obj.toString(), is(equalTo("{\"prop\":\"value\"}")));
}

That string might "magically" be converted to an object, just because it looks like JSON (so every string put feeds the value to the JSON parser):

@Test public void put_adds_raw_json_to_an_object() {
    JSONObject obj = new JSONObject();
    String value = "{ \"a\": 1 }";
    obj.put("prop", value);
    assertThat(obj.get("prop"), is(not(equalTo(value))));
    assertThat(obj.get("prop"), is(equalTo(JSONObject.fromObject(value))));
    assertThat(obj.toString(), is(equalTo("{\"prop\":{\"a\":1}}")));
}

And some other mangling is less obvious:

@Test public void put_strips_quotes_from_strings_sometimes() {
    JSONObject obj = new JSONObject();
    String value = "\"[value]\""; // also "\"{value}\""
    obj.put("prop", value);
    assertThat(obj.get("prop"), is(equalTo(value.replaceAll("\"", ""))));
    assertThat(obj.toString(), is(equalTo("{\"prop\":{\"a\":1}}")));
    assertThat(obj.toString(), is(equalTo("{\"prop\":\"[value]\"}")));
}

Do you want this property value to be a scalar or array?

@Test public void accumulate_one_scalar() {
    JSONObject obj = new JSONObject();
    obj.accumulate("a", 1);
    assertThat(obj.toString(), is(equalTo("{\"a\":1}")));
}
@Test public void accumulate_two_scalars() {
    JSONObject obj = new JSONObject();
    obj.accumulate("a", 1);
    obj.accumulate("a", 2);
    assertThat(obj.toString(), is(equalTo("{\"a\":[1,2]}")));
}
@Test public void accumulate_one_array() {
    JSONObject obj = new JSONObject();
    obj.accumulate("a", ImmutableList.of(1, 2));
    assertThat(obj.toString(), is(equalTo("{\"a\":[1,2]}")));
}
@Test public void accumulate_two_arrays() {
    JSONObject obj = new JSONObject();
    obj.accumulate("a", ImmutableList.of(1, 2));
    obj.accumulate("a", ImmutableList.of(3, 4));
    assertThat(obj.toString(), is(equalTo("{\"a\":[1,2,[3,4]]}")));
}

("fixed" in the org.json version, but incompatibly)

The central serialization method

public String toString() {
   if( isNullObject() ){
      return JSONNull.getInstance()
            .toString();
   }
   try{
      Iterator keys = keys();
      StringBuffer sb = new StringBuffer( "{" );

      while( keys.hasNext() ){
         if( sb.length() > 1 ){
            sb.append( ',' );
         }
         Object o = keys.next();
         sb.append( JSONUtils.quote( o.toString() ) );
         sb.append( ':' );
         sb.append( JSONUtils.valueToString( this.properties.get( o ) ) );
      }
      sb.append( '}' );
      return sb.toString();
   }catch( Exception e ){
      return null;
   }
}

Verify output, not intermediate objects

Lots of tests were written to get hold of a produced JSONObject and examine what was inside it. Refactoring these to format the object out to JSON encourages testing against an interface rather than an implementation.

/* old style */ @Test public String the_thing_has_a_price() {
    JSONObject obj = renderer.render(theThing());
    assertEquals(obj.getJSONObject("price").get("currency"), "GBP");
    assertEquals(obj.getJSONObject("price").get("amount"), 1.2);
}
/* new style */ @Test public String the_thing_has_a_price() {
    JSONObject obj = renderer.render(theThing());
    // allow single quotes in reference to avoid backslash-quote overload
    // btw JSONObject always uses relaxed parsing
    assertThat(obj.toString(),
               isEquivalentTo("{ 'price': { 'currency' : 'GBP',"
                                        + " 'amount': 1.2 }"));
}

Verifying output

Structural matchers if an equivalence test is too detailed.

@Test public String the_thing_has_a_price() {
    JSONObject obj = renderer.render(theThing());
    assertThat(obj,
               structuredAs(jsonObject()
                            .withProperty("price",
                                jsonObject()
                                .withProperty("currency", "GBP")
                                .withProperty("amount", jsonNumber(closeTo(1.2, 0.01))))));
}

In fact, these matchers are implemented using the Jackson tree model, so this is almost an interop check.

Two left standing

We are now using just two JSON librarys: the org.json generation of json-lib and Jackson.

Jackson
Widely-used and high-performance library, covering three distinct layers: streaming, generic document model and data binding. Flexible, with a complex API to match.
json-lib
Simple to understand and use, with mostly common-sense serialization of common types, but no attempt at flexibility beyond that. (Just stay away from that "accumulate" method)

Others out there

protobuf-json
We already expose services from Merc and Abacus as JSON using a protobuf-as-JSON transform.
JSR-353 jsonp/Kenai
Reference implementation of javax.json quite similar to org.json