jackson tutorial

Tutorial

5 Minute Tutorial for Jackson

(26-Jun-2009) NOTE:

The most up-to-date version of tutorial is now available as FasterXML Jackson Tutorial . This version will be removed in near future, so please update your links!

Introduction to 3 ways to process Json

Jackson Json processor allows you to read (parse) and write (generate) Json generate using 3 APIs:

  • Streaming API lets you read and write Json content with minimal overhead
    • org.codehaus.jackson.JsonParser for reading, org.codehaus.jackson.JsonGenerator for writing
    • Jackson Streaming API is similar to Stax API .
  • Data Binding allows you read Json into regular Java Objects, POJOs/beans, and write such Objects out as Json.
    • org.codehaus.jackson.map.ObjectMapper is the class that knows how to bind Json data to/from Java objects.
    • Data Binding is similar to JAXB for XML
  • Tree Model allows convenient access to Json content through an in-memory tree structure
    • As with data binding, org.codehaus.jackson.map.ObjectMapper is the thing to use for building tree from JSON (and writing out as JSON); trees consist of org.codehaus.jackson.map.JsonNode nodes.
    • Tree Model is similar to DOM trees for XML

Most other Json processing packages expose just a tree model, or a partial version of data binding. Jackson builds and serializes in-memory data models (Object, Tree) using fully streaming parser/generator components, resulting in unparalleled speed, without sacrificing convenience or correctness.

From usage perspective, these 3 methods can be summarized as:

  • Streaming API is the most performant (lowest overhead, fastest read/write)
  • Data Binding is the most convenient to use
  • Tree Model is the most flexible

Given these properties, let's consider these in the reverse order, starting with what is usually the most natural and convenient method for Java developers: Jackson Data Binding API.

Sample Usage: Data Binding (bean)

Data binding is implemented by ObjectMapper object that operates on streaming parsers and generators. Given sample Bean class (from article at http://www.cowtowncoder.com/blog/archives/2008/12/entry_121.html):

User.java
public class User {
    public enum Gender { MALE, FEMALE };

    public static class Name {
      private String _first, _last;

      public String getFirst() { return _first; }
      public String getLast() { return _last; }

      public void setFirst(String s) { _first = s; }
      public void setLast(String s) { _last = s; }
    }

    private Gender _gender;
    private Name _name;
    private boolean _isVerified;
    private byte[] _userImage;

    public Name getName() { return _name; }
    public boolean isVerified() { return _isVerified; }
    public Gender getGender() { return _gender; }
    public byte[] getUserImage() { return _userImage; }

    public void setName(Name n) { _name = n; }
    public void setVerified(boolean b) { _isVerified = b; }
    public void setGender(Gender g) { _gender = g; }
    public void setUserImage(byte[] b) { _userImage = b; }
}

And sample data like:

{
  "name" : { "first" : "Joe", "last" : "Sixpack" },
  "gender" : "MALE",
  "verified" : false,
  "userImage" : "Rm9vYmFyIQ=="
}

Given this, how do we get a User Object instance out of Json, stored in file "user.json"?

Like so:

ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally
User user = mapper.readValue(new File("user.json"), User.class);

Simple? We think so.

But how about the other direction: let's say we modify User instance to mark entry as verified ('user.setVerified(true)'), and want to save data back to the file.

Let's try this:

mapper.writeValue(new File("user-modified.json"), user);

Still looks quite straight-forward.

For more detailed description of how exactly Jackson Data Binding works, check out Data Binding Deep Dive .

Sample Usage: Data Binding ("untyped")

A commonly used alternative ways to do data binding is to use "untyped" binding. Untyped here means that we do not have strict Java classes (types) to bind to, but rather want to construct a loose "poor man's object" (combination of Maps, Lists and wrappers) to operate on. For example, with data from previous example we could do:

HashMap<String,Object> untyped = mapper.readValue(new File("user.json"), HashMap.class);

and get a result Object quite like one we would construct by:

HashMap<String,Object> untyped = new HashMap<String,Object>();
HashMap<String,String> nameStruct = new HashMap<String,String>();
nameStruct.put("first", "Joe");
nameStruct.put("last", "Sixpack");
untyped.put("name", nameStruct);
untyped.put("gender", "MALE");
untyped.put("verified", Boolean.FALSE);
untyped.put("userImage", "Rm9vYmFyIQ==");

Why? Because by specifying HashMap.class, we do not specify generic key/value types. This means that nominal types is Object.class, which in turns makes Jackson use simplest possible types (all of which are of type Object.class, obviously).

Sample Usage: Tree Model

Yet another way to get Objects out of JSON is to build a tree. This is similar to DOM trees for xml. The way Jackson builds trees is to use basic JsonNode base class, which exposes read access that is usually needed. Actual node types used are sub-classes; but the sub-type only needs to be used when modifying trees.

Trees can be read and written using either Streaming API (see below), or using ObjectMapper.

With ObjectMapper, you will do something like:

JsonTree.java
ObjectMapper m = new ObjectMapper();
// can either use mapper.readTree(JsonParser), or bind to JsonNode
JsonNode rootNode = m.readValue(new File("user.json"), JsonNode.class);
// ensure that "last name" isn't "Xmler"; if is, change to "Jsoner"
JsonNode nameNode = rootNode.path("name");
String lastName = nameNode.path("last").getTextValue().
if ("xmler".equalsIgnoreCase(lastName)) {
  ((ObjectNode)nameNode).put("last", "Jsoner");
}
// and write it out:
m.writeValue(new File("user-modified.json"), rootNode);

Sample Usage: Streaming API

Just for fun, let's implement the writing functionality (equivalent to earlier examples) using "raw" Streaming API:

WriteJSON.java
JsonFactory f = new JsonFactory();
JsonGenerator g = f.createJsonGenerator(new File("user.json"));

g.writeStartObject();
g.writeObjectFieldStart("name");
g.writeStringField("first", "Joe");
g.writeStringField("last", "Sixpack");
g.writeEndObject(); // for field 'name'
g.writeStringField("gender", Gender.MALE);
g.writeBooleanField("verified", false);
g.writeFieldName("userImage"); // no 'writeBinaryField' (yet?)
byte[] binaryData = ...;
g.writeBinary(binaryData);
g.writeEndObject();

Not horribly bad (esp. compared to amount of work needed for writing, say, equivalent XML content), but certainly more laborious than basic Object mapping.

On the other hand, you do have full control over each and every detail. And overhead is minimal: this is still a bit faster than using ObjectMapper; not a whole lot (perhaps 20-30% faster in common cases), but still.

And perhaps most importantly, output is done in streaming manner: except for some buffering, all content will be written out right away. This means that memory usage is also minimal.

How about parsing, then? Code could look something like:

ReadJSON.java
JsonFactory f = new JsonFactory();
JsonParser jp = f.createJsonParser(new File("user.json"));
User user = new User();
jp.nextToken(); // will return JsonToken.START_OBJECT (verify?)
while (jp.nextToken() != JsonToken.END_OBJECT) {
  String fieldname = jp.getCurrentName();
  jp.nextToken(); // move to value, or START_OBJECT/START_ARRAY
  if ("name".equals(fieldname)) { // contains an object
    Name name = new Name();
    while (jp.nextToken() != JsonToken.END_OBJECT) {
      String namefield = jp.getCurrentName();
      jp.nextToken(); // move to value
      if ("first".equals(namefield)) {
        name.setFirst(jp.getText());
      } else if ("last".equals(namefield)) {
        name.setLast(jp.getText());
      } else {
        throw new IllegalStateException("Unrecognized field '"+fieldname+"'!");
      }
    }
    user.setName(name);
  } else if ("gender".equals(fieldname)) {
    user.setGender(Gender.valueOf(jp.getText()));
  } else if ("verified".equals(fieldname)) {
    user.setVerified(jp.getCurrentToken() == JsonToken.VALUE_TRUE);
  } else if ("userImage".equals(fieldname)) {
    user.setUserImage(jp.getBinaryValue());
  } else {
    throw new IllegalStateException("Unrecognized field '"+fieldname+"'!");
  }
}

which is quite a bit more than you'll use with data binding.

One final trick: it is also possible to use data binding and tree model directly from JsonParser and JsonGenerator. To do this, have a look at methods:

  • JsonParser.readValueAs()
  • JsonParser.readValueAsTree()
  • JsonGenerator.writeObject()
  • JsonGenerator.writeTree()

which do about what you might expect them to do.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值