JSON, or JavaScript Object Notation, is:
A simple, lightweight data interchange format.
A simpler, lighter alternative to XML.
Easy to generate with println()
or with one of several APIs.
Recognized directly by the JavaScript parser in all web browsers.
Supported with add-on frameworks for all common languages (Java, C/C++, Perl, Ruby, Python, Lua, Erlang, Haskell, to name a few); a ridiculously long list of supported languages (including two dozen parsers for Java alone) is right on the homepage.
A simple JSON message might look like this:
json/src/main/resources/json/softwareinfo.json/
{
"name"
:
"robinparse"
,
"version"
:
"1.2.3"
,
"description"
:
"Another Parser for JSON"
,
"className"
:
"RobinParse"
,
"contributors"
:
[
"Robin Smythe"
,
"Jon Jenz"
,
"Jan Ardann"
]
}
As you can see, the syntax is simple, nestable, and amenable to human inspection.
The JSON home page provides a concise summary of JSON syntax.
There are two kinds of structure: JSON Objects (maps) and JSON Arrays (lists).
JSON Objects are sets of name and value pairs, which can be represented
either as a java.util.Map
or as the properties of
a Java object. For example, the fields of a LocalDate
(see Recipe 6.1) object
for April 1, 2019, might be represented as:
{
"year"
:
2019
,
"month"
:
4
,
"day"
:
1
}
JSON Arrays are ordered lists, represented in Java either as arrays or as java.util.List
s.
A list of two dates might look like this:
{
[{
"year"
:
2019
,
"month"
:
4
,
"day"
:
1
}
,
{
"year"
:
2019
,
"month"
:
5
,
"day"
:
15
}
]
}
JSON is free-format, so the preceding could also be written, with some loss of human-readability but no loss of information or functionality, as:
{
[{
"year"
:
2019
,
"month"
:
4
,
"day"
:
1
}
,
{
"year"
:
2019
,
"month"
:
5
,
"day"
:
15
}
]}
Hundreds of parsers have, I’m sure, been written for JSON. A few that come to mind in the Java world include the following:
stringtree.org
Very small and light weight
json.org parser
Widely used because it’s free and has a good domain name
jackson.org parser
Widely used because it’s very powerful, and used with Spring Framework and with JBoss RESTEasy and Wildfly
javax.json
Oracle’s official but currently EE-only standard
This chapter shows several ways of processing JSON data using some of the various APIs just listed.
The “official” javax.json
API is only included in the Java EE, not the Java SE,
so it is unlikely to see very much use on the client side.
This API uses some names in common with the org.json
API, but not enough to be considered “compatible.”
Because this is a book for client-side Java developers, nothing will be made of the ability to process JSON directly in server-generated browser-based JavaScript, though this can be very useful in building enterprise applications.
You want to generate JSON without bothering to use an API.
Get the data you want, and use println()
or String.format()
as appropriate.
If you are careful, you can generate JSON data yourself. For the utterly trivial cases,
you can just use the PrintWriter.println()
or String.format()
.
For significant volumes, however, it’s usually better to use one of the APIs.
This code prints the year, month, and date from a LocalTime
object (see Recipe 6.1).
Some of the JSON formatting is delegated to the toJson()
object:
/**
* Convert an object to JSON, not using any JSON API.
* BAD IDEA - should use an API!
*/
public
class
LocalDateToJsonManually
{
private
static
final
String
OPEN
=
"{"
;
private
static
final
String
CLOSE
=
"}"
;
public
static
void
main
(
String
[]
args
)
{
LocalDate
dNow
=
LocalDate
.
now
();
System
.
out
.
println
(
toJson
(
dNow
));
}
public
static
String
toJson
(
LocalDate
dNow
)
{
StringBuilder
sb
=
new
StringBuilder
();
sb
.
append
(
OPEN
).
append
(
" "
);
sb
.
append
(
jsonize
(
"year"
,
dNow
.
getYear
()));
sb
.
append
(
jsonize
(
"month"
,
dNow
.
getMonth
()));
sb
.
append
(
jsonize
(
"day"
,
dNow
.
getDayOfMonth
()));
sb
.
append
(
CLOSE
).
append
(
" "
);
return
sb
.
toString
();
}
public
static
String
jsonize
(
String
key
,
Object
value
)
{
return
String
.
format
(
""%s": "%s", "
,
key
,
value
);
}
}
Of course, this is an extremely trivial example. For anything more involved, or for the common case of having to parse JSON objects, using one of the frameworks will be easier on your nerves.
You want to read and/or write JSON using a full-function JSON API.
Use Jackson, the full-blown JSON API.
Jackson provides many ways of working. For simple cases, you can have POJO (plain old Java objects) converted to/from JSON more-or-less automatically, as is illustrated in Example 14-1.
public
class
ReadWriteJackson
{
public
static
void
main
(
String
[
]
args
)
throws
IOException
{
ObjectMapper
mapper
=
new
ObjectMapper
(
)
;
String
jsonInput
=
"{"id":0,"firstName":"Robin","lastName":"Wilson"}"
;
Person
q
=
mapper
.
readValue
(
jsonInput
,
Person
.
class
)
;
System
.
out
.
println
(
"Read and parsed Person from JSON: "
+
q
)
;
Person
p
=
new
Person
(
"Roger"
,
"Rabbit"
)
;
System
.
out
.
(
"Person object "
+
p
+
" as JSON = "
)
;
mapper
.
writeValue
(
System
.
out
,
p
)
;
}
}
Create a Jackson ObjectMapper
which can map POJOs to/from JSON.
Map the string jsonInput
into a Person
object with one call to readValue()
.
Convert the Person
object p
into JSON with one call to writeValue()
.
Running this example produces the following output:
Read and parsed Person from JSON: Robin Wilson Person object Roger Rabbit as JSON = {"id":0,"firstName":"Roger", "lastName":"Rabbit","name":"Roger Rabbit"}
As another example, this code reads the “parser description” example file
that opened this chapter; notice the declaration List<String>
for the
array of contributors:
public
class
SoftwareParseJackson
{
final
static
String
FILE_NAME
=
"/json/softwareinfo.json"
;
public
static
void
main
(
String
[
]
args
)
throws
Exception
{
ObjectMapper
mapper
=
new
ObjectMapper
(
)
;
InputStream
jsonInput
=
SoftwareParseJackson
.
class
.
getResourceAsStream
(
FILE_NAME
)
;
if
(
jsonInput
=
=
null
)
{
throw
new
NullPointerException
(
"can't find "
+
FILE_NAME
)
;
}
SoftwareInfo
sware
=
mapper
.
readValue
(
jsonInput
,
SoftwareInfo
.
class
)
;
System
.
out
.
println
(
sware
)
;
}
}
Running this example produces the following output:
Software: robinparse (1.2.3) by [Robin Smythe, Jon Jenz, Jan Ardann]
Of course there are cases where the mapping gets more involved; for this purpose, Jackson provides a set of annotations to control the mapping. But the default mapping is pretty good!
There is also a “streaming” API for Jackson; refer to their website for details.
You want to read/write JSON using a mid-sized, widely used JSON API.
Consider using the org.json API; it’s widely used and is also used in Android.
The org.json package is not as advanced as Jackson, nor as high-level; it makes you think and work in terms of the underlying JSON abstractions instead of at the Java code level. For example, here is the org.json version of reading the software description from the opening of this chapter:
public
class
SoftwareParseOrgJson
{
final
static
String
FILE_NAME
=
"/json/softwareinfo.json"
;
public
static
void
main
(
String
[
]
args
)
throws
Exception
{
InputStream
jsonInput
=
SoftwareParseOrgJson
.
class
.
getResourceAsStream
(
FILE_NAME
)
;
if
(
jsonInput
=
=
null
)
{
throw
new
NullPointerException
(
"can't find"
+
FILE_NAME
)
;
}
JSONObject
obj
=
new
JSONObject
(
new
JSONTokener
(
jsonInput
)
)
;
System
.
out
.
println
(
"Software Name: "
+
obj
.
getString
(
"name"
)
)
;
System
.
out
.
println
(
"Version: "
+
obj
.
getString
(
"version"
)
)
;
System
.
out
.
println
(
"Description: "
+
obj
.
getString
(
"description"
)
)
;
System
.
out
.
println
(
"Class: "
+
obj
.
getString
(
"className"
)
)
;
JSONArray
contribs
=
obj
.
getJSONArray
(
"contributors"
)
;
for
(
int
i
=
0
;
i
<
contribs
.
length
(
)
;
i
+
+
)
{
System
.
out
.
println
(
"Contributor Name: "
+
contribs
.
get
(
i
)
)
;
}
}
}
Create the JSONObject
from the input.
Retrieve individual String
fields.
Retrieve the JSONArray
of contributor names.
org.json.JSONArray
doesn’t implement Iterable so you can’t use a foreach loop.
Running it produces the expected output:
Software Name: robinparse Version: 1.2.3 Description: Another Parser for JSON Class: RobinParse Contributor Name: Robin Smythe Contributor Name: Jon Jenz Contributor Name: Jan Ardann
JSONObject
and JSONArray
use their toString()
method to produce (correctly formatted) JSON strings.
For example:
public
class
WriteOrgJson
{
public
static
void
main
(
String
[
]
args
)
{
JSONObject
jsonObject
=
new
JSONObject
(
)
;
jsonObject
.
put
(
"Name"
,
"robinParse"
)
.
put
(
"Version"
,
"1.2.3"
)
.
put
(
"Class"
,
"RobinParse"
)
;
String
printable
=
jsonObject
.
toString
(
)
;
System
.
out
.
println
(
printable
)
;
}
}
Nice that it offers a “fluent API” to allow chaining of method calls
toString()
converts to textual JSON representation.
Running this produces the following:
{
"Name"
:
"robinParse"
,
"Class"
:
"RobinParse"
,
"Version"
:
"1.2.3"
}
The org.json API javadoc is online at http://www.json.org/java/index.html.
You want to read/write JSON using a mid-sized, standards-conforming JSON API.
Consider using JSON-B, the new Java standard (JSR-367).
JSON-B (JSON Binding) API is designed to make it simple to read/write Java POJOs. This is neatly illustrated by the code in Example 14-2.
public
class
ReadWriteJsonB
{
public
static
void
main
(
String
[
]
args
)
throws
IOException
{
Jsonb
jsonb
=
JsonbBuilder
.
create
(
)
;
// Read
String
jsonInput
=
"{"id":0,"firstName":"Robin","lastName":"Williams"}"
;
Person
rw
=
jsonb
.
fromJson
(
jsonInput
,
Person
.
class
)
;
System
.
out
.
println
(
rw
)
;
String
result
=
jsonb
.
toJson
(
rw
)
;
System
.
out
.
println
(
result
)
;
}
}
<1>Create a Jsonb object, your gateway to JSON-B services.
<2>Obtain a JSON string, and convert it to a Java object using jsonb.fromJson()
.
<3>Convert a Person object back to a JSON string using the inverse jsonb.toJson()
.
Note that the methods are sensibly named, and that no annotations are needed on the Java entity class to make this work. However, there is an API that allows us to customize it. For example, the “fullname” property is really just a convenience for concatenating the first and lastname with a space between. As such, it’s completely redundant, and does not need to be transmitted over a JSON network stream. However, running the program produces this output:
{
"firstName"
:
"Robin"
,
"fullName"
:
"Robin Williams"
,
"id"
:
0
,
"lastName"
:
"Williams"
}
We need only add the @JsonbTransient
annotation to the getFullName()
accessor
in the Person class to eliminate the redundancy; running the program now
produces this smaller output:
{
"firstName"
:
"Robin"
,
"id"
:
0
,
"lastName"
:
"Williams"
}
As with most other JSON apis, there is full support for customization, ranging from the simple annotation shown here up to writing complete custom serializer/deserializer helpers. See the JSON-B spec page, the JSON-B home page, and this longer tutorial online.
You have a JSON document and want to extract only selected values from it.
Use javax.json’s implementation of JSON Pointer, the standard API for extracting selected elements from JSON.
The Internet Standard RFC 6901 spells out in detail
the syntax for JSON Pointer, a language-independent syntax for matching elements in JSON documents.
Obviously inspired by the XML syntax XPath, JSON Pointer is a bit simpler than XPath
because of JSON’s inherent simpllicity. Basically a JSON Pointer is a string that identifies
an element (either simple or array) within a JSON document.
The javax.json
package provides an object model API somewhat similar to
the XML DOM API for Java, letting you create immutable objects to represent
objects (via JsonObjectBuilder
and JsonArrayBuilder
) or to read them
from JSON string format via a Reader or InputStream.
JSON Pointers begin with a “/” (inherited from XPath), followed by the name of the element or sub-element we want to look for. If we extend our Person example to add an array of roles the comedian played, looking like this:
{
"firstName"
:
"Robin"
,
"lastName"
:
"Williams"
,
"age"
:
63
,
"id"
:
0
,
"roles"
:[
"Mork"
,
"Mrs. Doubtfire"
,
"Patch Adams"
]}
Then the following pointers should generate the given matches:
/firstName => Robin /age => 63 /roles => ["Mork","Mrs. Doubtfire","Patch Adams"] /roles/1 => "Mrs. Doubtfire"
The program in Example 14-3 demonstrates this.
public
class
JsonPointerDemo
{
public
static
void
main
(
String
[
]
args
)
{
String
jsonPerson
=
"{"firstName":"Robin","lastName":"Williams","
+
""age": 63,"
+
""id":0,"
+
""roles":["Mork", "Mrs. Doubtfire", "Patch Adams"]}"
;
System
.
out
.
println
(
"Input: "
+
jsonPerson
)
;
JsonReader
rdr
=
Json
.
createReader
(
new
StringReader
(
jsonPerson
)
)
;
JsonStructure
jsonStr
=
rdr
.
read
(
)
;
rdr
.
close
(
)
;
JsonPointer
jsonPointer
;
JsonString
jsonString
;
jsonPointer
=
Json
.
createPointer
(
"/firstName"
)
;
jsonString
=
(
JsonString
)
jsonPointer
.
getValue
(
jsonStr
)
;
String
firstName
=
jsonString
.
getString
(
)
;
System
.
out
.
println
(
"/firstName => "
+
firstName
)
;
JsonNumber
num
=
(
JsonNumber
)
Json
.
createPointer
(
"/age"
)
.
getValue
(
jsonStr
)
;
System
.
out
.
println
(
"/age => "
+
num
+
"; a "
+
num
.
getClass
(
)
.
getName
(
)
)
;
jsonPointer
=
Json
.
createPointer
(
"/roles"
)
;
JsonArray
roles
=
(
JsonArray
)
jsonPointer
.
getValue
(
jsonStr
)
;
System
.
out
.
println
(
"/roles => "
+
roles
)
;
System
.
out
.
println
(
"JsonArray roles.get(1) => "
+
roles
.
get
(
1
)
)
;
jsonPointer
=
Json
.
createPointer
(
"/roles/1"
)
;
jsonString
=
(
JsonString
)
jsonPointer
.
getValue
(
jsonStr
)
;
System
.
out
.
println
(
"/roles/1 => "
+
jsonString
)
;
}
}
Create the JsonStructure, the gateway into this API, from a JsonReader, using a StringReader.
Create a JSON Pointer for the firstName
element, get the JsonString from the element’s value.
Since getValue() will throw an exception if the element is not found, use
jsonPointer.containsValue(jsonStr)
to check first, if not sure.
Same for age
, but using more fluent syntax.
If you print the class name for the match in /age
, it will report an
implementation-specific implementation class, such as
org.glassfish.json.JsonNumberImpl$JsonIntNumber
.
Change the age in the XML from 63 to 63.5 and it will print a class with BigDecimal in its name.
Either way, toString()
on this object will return just the numeric value.
In the JSON file, roles
is an array. Thus, getting it using a JSON Pointer
should return a JsonArray object, so we cast it to a reference of that type.
This behaves somewhat like an immutable List
implementation, so we call get()
.
JSON array indices start at zero, as in Java.
Retrieve the same array element directly, using a pattern with “/1” to mean the numbered element in the array.
It is possible (but fortunately not common) for a JSON element name to contain special
characters such as a slash. Most characters are not special to
JSON Pointer, but to match a name containing a slash (/),
the slash must be entered as ~1, and since that makes the
tilde (~) special, tilde characters must be entered as ~0.
Thus if the Person JSON file had an element like: "ft/pt/~"
,
you would look for it with Json.createPointer("/ft~1pt~1~0");
.
The JSON Pointer API has additional methods which let you modify values and add/remove elements.
The offical home page for JSON Processing API javax.json
-which includes JSON Pointer-is at
jakarta.ee.
The javadoc for javax.json
is linked to from that page.
Many APIs exist for Java. Jackson is the biggest and most powerful; org.json and javax.json and JSON-B are in the middle; StringTree (which I didn’t give an example of because it doesn’t have a Maven Artifact available) is the smallest. For a list of these and other JSON APIs, consult http://www.json.org/java/ and scroll past the syntax summary.
18.119.131.10