JSON encode/decode classes for Java
Does the world need another Java JSON library? Perhaps not, but let me explain.
This work was started before any of the currently-popular libraries had achieved any sort of
prominence. I had a requirement for classes to parse incoming JSON data, to hold the JSON
representation in an internal form that was easy to access, and to output syntactically-correct
JSON results. It seemed a reasonably simple task, and a good test of the ParseText
class,
part of my javautil
library.
When I later encountered the more widely used libraries I was struck by how complicated they seemed. And how large. And one of them described itself as "fastJSON" which I saw as a challenge.
There is a place for software that does a limited range of operations, and does them very well. With that in mind, I decided to continue development of my JSON library.
Keep It Simple --- don't add complications that will rarely be used; keep your software easy to understand and easy to use.
Follow Standards --- where there is an existing standard that could be used, adopt that standard rather than creating a new one.
Make It Fast --- one of the main reasons for using a limited-functionality library is performance.
In the light of those principles, the following design decisions were adopted:
- Have a single interface
JSONValue
implemented by each of the five types of JSON data: number, string, boolean, array and object. - Ensure that the array class
JSONArray
implementsList<JSONValue>
, so that anyone familiar with that interface could easily access the contents of the array. - Ensure that the object class
JSONObject
implementsMap<String, JSONValue>
, for similar reasons. - Limit the implementation to the current requirements.
The implementation follows a conventional Java pattern. There is a main class JSON
which
contains a set of static methods to parse a string (in memory, or in a file or stream) into the
internal form. That internal form is a classic tree structure, where each node is either a leaf
node (string, number or boolean), or a composite (array or object) which contains more nodes.
The nodes all implement the JSONValue
interface, which specifies two methods:
String toJSON();
--- convert to the JSON external representationvoid appendJSON(Appendable a)
--- append the JSON form to anAppendable
(for example, aStringBuilder
or aWriter
).
The second method is an optimisation, which avoids the need to create an individual String
for each node when outputting a composite.
There is extensive JavaDoc which should assist in the use of the library.
On most benchmarks, the library significantly out-performs its competitors. For example:
The library is in the Maven Central Repository; the co-ordinates are listed in the Dependency Specification section below.
Note that the groupId
has changed from previous versions, which used net.pwall.util
.
From version 2.0 onwards, jsonutil
requires Java 8. Those seeking a version compatible with Java 7 should use:
<dependency>
<groupId>net.pwall.util</groupId>
<artifactId>jsonutil</artifactId>
<version>1.6</version>
</dependency>
There are several static methods of the JSON
class to parse a string, file or input stream
into a JSONValue
. The result may be null
if the input consists simply of the token
null
, or one of the seven simple JSON forms:
JSONString
JSONInteger
JSONLong
JSONDouble
JSONFloat
JSONZero
(an optimisation to improve handling of zero values)JSONBoolean
or one of the two composite forms:
JSONArray
JSONObject
All of the simple classes have a getValue()
method to retrieve the value (this is not specified by
the interface because the return type differs in each case). Also, the numeric classes all
extend the Number
class, so the accessors intValue()
, doubleValue()
etc. may be used
to retrieve the value in a particular form. And the toString()
methods on these classes all
return the string representation of the value, not the JSON.
The simplest way to use the JSONArray
and JSONObject
classes is to treat them as
List<JSONValue>
and Map<String, JSONValue>
respectively. In addition, there are several
convenience methods (getString()
, getObject()
etc.) for use when the type of the value is
known in advance. Also, several overloaded forms of addValue()
(for JSONArray
) and
putValue()
(for JSONObject
) exist, to simplify adding values to arrays and objects.
The two static methods JSONArray.create()
and JSONObject.create()
exist to facilitate
the "fluent" style of coding (see example below).
To create the JSON string value of an object, the toJSON()
method may be called; this will
serialize the value, recursively calling toJSON()
on any member items as necessary. To avoid
the unnecessary creation of a large number of intermediate string objects, the appendJSON()
method may be used to append to an existing Stringbuilder
etc. It should be noted that this
is also useful for serializing directly to an output stream, e.g. a PrintStream
or Writer
.
To parse and process an incoming JSON object, assuming the JSON is of the form:
{
"id": "A23456",
"qty": 12,
"value": 60.00
}
The following code will process that JSON:
JSONObject jsonObject = (JSONObject)JSON.parse(str);
String accountId = jsonObject.getString("id");
int quantity = jsonObject.getInt("qty");
double value = jsonObject.getDouble("value");
(The cast is required because JSON.parse(str)
returns JSONValue
; the developer has the
option of testing the type of the returned value or allowing the system to throw a
ClassCastException
.)
Note: from version 1.6 onwards, additional convenience methods have been provided to cast the
result to JSONArray
or JSONObject
. The first line of the above example may now be
written:
JSONObject jsonObject = JSON.parseObject(str);
To create an object of that form and then output it:
JSONObject jsonObject = JSONObject.create().putValue("id", "A23456").putValue("qty", 12).
putValue("value", 60.00);
System.out.println(jsonObject.toJSON());
And if that JSON object was an item in an array of objects, the array could be processed by:
JSONArray jsonArray = (JSONArray)JSON.parse(str);
for (JSONObject jsonObject : jsonArray.objects()) {
String accountId = jsonObject.getString("id");
// etc...
}
This is just a taste of what is available; see the JavaDoc for more information.
JSON uses base 10 to represent numbers. This works perfectly well for integer values, e.g. 12345, but it can cause problems when representing fractions.
Most people are familiar with the fact that the value 1/3 (one third) can not be represented in a finite number of
decimal places – the last digit is recurring.
What is less well-known is that the value 1/10 (one tenth) can not be represented as a finite binary fraction, and this
means that all binary representations of decimal fractions are potentially incorrect.
As a result, Java double
and float
variables are potentially problematic as representations of parsed fractional
decimal values.
To eliminate this problem the JSONDecimal
class has been created, storing its value in the form of a BigDecimal
.
From version 3.0 of this library, all parsing operations will create JSONDecimal
objects for non-integer number values
instead of JSONDouble
.
Also, all numeric values (classes that derive from JSONNumberValue
) now include toBigDecimal()
and toBigInteger()
methods to allow the value to be retrieved without intermediate conversion to binary floating point.
From version 3.1 of this library, the JSONDecimal
class is also used for integer values that are too large for the
Long
class.
From version 5.0 onwards, the method get()
on each of the JSONValue
objects for the primitive types has been
renamed getValue()
.
As well as being more idiomatically consistent with other forms of Java usage, this also allows Kotlin code to access
values using the value
property.
The latest version of the library is 5.1, and it may be obtained from the Maven Central repository.
<dependency>
<groupId>net.pwall.json</groupId>
<artifactId>jsonutil</artifactId>
<version>5.1</version>
</dependency>
implementation "net.pwall.json:jsonutil:5.1"
implementation("net.pwall.json:jsonutil:5.1")
Peter Wall
2023-06-29