12

I have a JSON file which can have multiple types.

For example:

{
    "dog": {
        "owner" : "John Smith",
        "name" : "Rex",
        "toys" : {
            "chewtoy" : "5",
            "bone" : "1"
        }
    },
    "person": {
        "name" : "John Doe",
        "address" : "23 Somewhere Lane"
    }
    // Further examples of dogs and people, and a few other types.
}

I want to parse these into objects. ie. I want to create a Dog object with owner/name/toys attributes, and person with name/address attributes, and use Jackson to read through and create objects out of them.

The ordering matters - I need to know that Rex came before John Doe, for example. I would prefer to do with a stream like approach (ie. read and parse Rex into the Dog object, do something with it, then discard it, then move onto to John Doe). So I need a stream based approach.

I can't figure out how to use both the stream reading API (to go through in order) and the ObjectMapper interface (in order to create Java objects out of JSON) to accomplish this.

4
  • 1
    (1) Your question about stream or SAX-like parsing is totally valid. +1. But (2) your data is not well-designed if ordering matters in a dictionary. JSON dictionaries are inherently unordered. If you need an order, use an array and encode the type (dog/person) information as an attribute as well. Commented Nov 20, 2012 at 3:32
  • Hmm. The data source is likely unchangeable (not under my control). When you say use an array and encode the information, do you mean do this manually? (Read through all tokens and create appropriate objects calling appropriate setters) Or is there a way to do this using Jackson? Commented Nov 20, 2012 at 3:38
  • I meant the JSON data should come in the form of an array if order matters, with types encoded with each object. But if you don't control the JSON data, then nevermind-- you'll have to deal with the stream parsing as you suggest. :) I don't have the answer to your actual question, although someone else here surely will. But know that whoever came up with this JSON format is playing with fire. It's very fragile to rely on order of keys in a dictionary, to say nothing of the pain in the arse it creates for people (you) who are trying to deal with it-- it goes against the grain of most tools. Commented Nov 20, 2012 at 4:03
  • Thanks for the response. In the event that I do get the data changed, so that the objects are stored in as an array, is it then possible to do a combination stream/object mapper parsing approach? Commented Nov 20, 2012 at 4:15

1 Answer 1

12

To do this, you need to use an object mapper with your factory

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
...
private static ObjectMapper mapper = new ObjectMapper();
private static JsonFactory factory = mapper.getJsonFactory();

Then create a parser for the input.

JsonParser parser = factory.createJsonParser(in);

Now you can mix calls to parser.nextToken() and calls to parser.readValueAs(Class c). Here is an example that gets the classes from a map:

Map<String, Class<?>> classMap = new HashMap<String, Class<?>>();
classMap.put("dog", Dog.class);
classMap.put("person", Person.class);

InputStream in = null;
JsonParser parser = null;
List<Object> results = new ArrayList<Object>();
try {
  in = this.getClass().getResourceAsStream("input.json");
  parser = factory.createJsonParser(in);
  parser.nextToken();// JsonToken.START_OBJECT
  JsonToken token = null;
  while( (token = parser.nextToken()) == JsonToken.FIELD_NAME ) {
    String name = parser.getText();
    parser.nextToken(); // JsonToken.START_OBJECT
    results.add(parser.readValueAs(classMap.get(name)));
  }
  // ASSERT: token = JsonToken.END_OBJECT
}
finally {
  IOUtils.closeQuietly(in);
  try {
    parser.close();
  }
  catch( Exception e ) {}
}
Sign up to request clarification or add additional context in comments.

2 Comments

I think I see now. When I was trying to do that initially, I was struggling because I wouldn't know what object I was at ("dog" or "person"), but if the data is formatted as an array with the dog/person values as values in a "type" attribute, I'd be able to do this. Thanks everyone.
A good answer. But, it would be great if you can rewrite your code snippet using latest Jackson library.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.