Chapter 14. Processing JSON Data

14.0 Introduction

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

14.1 Generating JSON Directly

Problem

You want to generate JSON without bothering to use an API.

Solution

Get the data you want, and use println() or String.format() as appropriate.

Discussion

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.

14.2 Parsing and Writing JSON with Jackson

Problem

You want to read and/or write JSON using a full-function JSON API.

Solution

Use Jackson, the full-blown JSON API.

Discussion

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.

Example 14-1. json/src/main/java/json/ReadWriteJackson.java - Reading and Writing POJOs with Jackson
public class ReadWriteJackson {

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();                1

        String jsonInput =                                       2
                "{"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");                3
        System.out.print("Person object " + p +" as JSON = ");
        mapper.writeValue(System.out, p);
    }
}
1

Create a Jackson ObjectMapper which can map POJOs to/from JSON.

2

Map the string jsonInput into a Person object with one call to readValue().

3

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(); 1

        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);
    }

}
1

The ObjectMapper does the actual parsing of the JSON input.

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.

14.3 Parsing and Writing JSON with org.json

Problem

You want to read/write JSON using a mid-sized, widely used JSON API.

Solution

Consider using the org.json API; it’s widely used and is also used in Android.

Discussion

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));      1
        System.out.println("Software Name: " + obj.getString("name"));    2
        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");            3
        for (int i = 0; i < contribs.length(); i++) {                     4
            System.out.println("Contributor Name: " + contribs.get(i));
        }
    }

}
1

Create the JSONObject from the input.

2

Retrieve individual String fields.

3

Retrieve the JSONArray of contributor names.

4

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").        1
            put("Version", "1.2.3").
            put("Class", "RobinParse");
        String printable = jsonObject.toString();    2
        System.out.println(printable);
    }
}
1

Nice that it offers a “fluent API” to allow chaining of method calls

2

toString() converts to textual JSON representation.

Running this produces the following:

{"Name":"robinParse","Class":"RobinParse","Version":"1.2.3"}

See Also

The org.json API javadoc is online at http://www.json.org/java/index.html.

14.4 Parsing and Writing JSON with JSON-B

Problem

You want to read/write JSON using a mid-sized, standards-conforming JSON API.

Solution

Consider using JSON-B, the new Java standard (JSR-367).

Discussion

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.

Example 14-2. json/src/main/java/json/ReadWriteJsonB.java - Reading/Writing JSON with JSON-B
public class ReadWriteJsonB {

    public static void main(String[] args) throws IOException {

        Jsonb jsonb = JsonbBuilder.create();            1

        // Read
        String jsonInput =                                2
                "{"id":0,"firstName":"Robin","lastName":"Williams"}";
        Person rw = jsonb.fromJson(jsonInput, Person.class);
        System.out.println(rw);

        String result = jsonb.toJson(rw);                3
        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"}

See Also

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.

14.5 Finding JSON Elements with JSON-Pointer

Problem

You have a JSON document and want to extract only selected values from it.

Solution

Use javax.json’s implementation of JSON Pointer, the standard API for extracting selected elements from JSON.

Discussion

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.

Example 14-3. json/src/main/java/json/JsonPointerDemo.java
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));     1
        JsonStructure jsonStr = rdr.read();
        rdr.close();

        JsonPointer jsonPointer;
        JsonString jsonString;

        jsonPointer = Json.createPointer("/firstName");                2
        jsonString = (JsonString)jsonPointer.getValue(jsonStr);
        String firstName = jsonString.getString();
        System.out.println("/firstName => " + firstName);

        JsonNumber num =                                            3
                (JsonNumber) Json.createPointer("/age").getValue(jsonStr);
        System.out.println("/age => " + num + "; a " + num.getClass().getName());

        jsonPointer = Json.createPointer("/roles");                    4
        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");                5
        jsonString = (JsonString)jsonPointer.getValue(jsonStr);
        System.out.println("/roles/1 => " + jsonString);
    }
}
1

Create the JsonStructure, the gateway into this API, from a JsonReader, using a StringReader.

2

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.

3

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.

4

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.

5

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");.

See Also

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.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.119.81