Skip to content

Chapter Intro Read JSON

Tatu Saloranta edited this page Sep 2, 2016 · 3 revisions

Jackson Manual: Introduction - Read JSON

By far the most common use case for Jackson tool set is that of reading JSON encoded data into something that can be processed by Java (*) code. Let's start with a simple JSON document, this one from Wikipedia:

{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": 10021
  },
  "phoneNumber": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "fax",
      "number": "646 555-4567"
    }
  ],
  "gender": {
    "type": "male"
  }
}

and see how you can read it in Java using Jackson.

(*) and other JVM languages like Scala, Kotlin -- but more on these later on!

Read as... ?

Some JSON libraries only have one way to deal with such content from Java, and results will be given you in that one specific form. But with Jackson you have a choice of multiple "processing models": without yet going into full detail (*), we have 3 common alternatives:

  • Read as "Plain Old Java Object" ("POJO", sometimes referred to as "Java Bean"): class defined by you, with settable/gettable properties or public fields
  • Read as Java Map, where property values are "simple" types like java.lang.Number, java.lang.String, java.lang.Boolean, java.util.Map and java.util.List.
  • Read as Jackson-specific data node, JsonNodes (known as "Tree Model" in Jackson documentation; or as Documents or even DOM in some other articles)

All models have their benefits and drawbacks and some developers prefer one approach over others. For now let's just have an overview of each approach and see how code compares.

(*) fear not; we shall in [Chapter X]

Read JSON as POJO

If we want to read JSON as a POJO, we need to have a Java class definition. Simplest one would look like so (*):

public class User {
  public String firstName, lastName;
  public int age;
  public Address address;
  public List<Phone> phoneNumber;
  public Gender gender;
}

public class Address {
  public String streetAddress;
  public String city;
  public State state;
  public int postalCode;
}
public enum State {
   NY, WA; // and probably all the rest....
}
public class Phone {
  public String type;
  public String number;
}
public class Gender {
  public String type;
}

and code to read JSON in would be:

// presuming we have `mapper` available:
User user = mapper.readValue(new File("stuff.json"), User.class);

and then values may be accessed just like any other Java objects:

String firstName = user.firstName;
// or, if you had getter:
// String firstName = user.getFirstName()
int postalCode = user.address.postalCode;

(*) but you can do it in much more elaborate way too; add getters/setters -- here we focus on brevity and clarity

Read JSON as simple Map

If you want to avoid having to create matching class structure (or if the data does not appear to have regular structure to model) you may instead want to read JSON as basic java.util.Map:

ObjectMapper mapper = new ObjectMapper(); // same for all cases; reusable, construction not repeated
Map<String,Object> map = mapper.readValue(new File("stuff.json"), Map.class);

after which you can access values via Map API:

String firstName = (String) map.get("firstName");
Map<String,Object> address = (Map<String,Object> map.get("streetAddress");
int postalCode = ((Number) address.get("postalCode")).intValue();

This requires much more casting, and for robust code null checks; otherwise there is nothing very complex about this approach.

Read JSON as JsonNode

The third approach; and one that many other JSON libraries expose as their only one, is to read JSON into library-specific Node/Tree model. With Jackson this means JsonNode:

// presuming we have `mapper` available:
JsonNode userNode = mapper.readTree(new File("stuff.json"));

and access is somewhat similar to Map case:

String firstName = userNode.get("firstName").asText();
Map<String,Object> address = (Map<String,Object> map.get("streetAddress");
int postalCode = userNode.path("address").path("postalCode").asInt();
// or, with JsonPointer (spoiler alert!)
// int postalCode = userNode.at("/address/postalCode").asInt();

but with less need for casts or checks: we'll get more into details later on; but for now it is enough to know that path() NEVER returns null and de-referencing of values is safe(r).

Read from... ?

While previous examples assumed you are reading JSON from a File, this is just one of many possibilities. Most commonly used input sources are directly supported with overloaded methods; for example:

User user = mapper.readValue(new URL("http://foo.bar/stuff.json", User.class); // from URL
int[] numbers = mapper.readValue("[ 1, 2, 3 ]", int[].class); // from literal JSON String
InputStream in = ... ;
byte[] buffered = ... ; // read from external source, or cached or...

User user2 = mapper.readValue(in, User.class); // uses char auto-detection, defaulting to UTF-8
user2 = mapper.readValue(new InputStreamReader(in, "ISO-8859-1"), User.class); // if knonw to be Latin-1
User user3 = mapper.readValue(buffered, User.class);

DataInput chunky = ....; // by Hadoop, for example
User user4 = mapper.readValue(chunky, User.class);

You may notice a pattern there, really. And if the specific input source type is not directly supported you can (and need to) map it into one of canonical input source types: java.io.InputStream (preferred) or java.io.Reader.