简体   繁体   中英

How to parse a JSON array of objects in Java

I've never attempted to parse JSON before. I have some books in a JSON format like this:

{
"Search": {
    "Books": [
        {
            "isbn": "1849830347",
            "title": "American Assassin",
            "author": "Vince Flynn",
            "price": 7.99
        },
        {
            "isbn": "0857208683",
            "title": Kill Shot",
            "author": "Vince Flynn",
            "price": 5.99
        },
        ...
    }
}

What I want is to parse them and end up with a List of populated Book objects. I have played around with Jackson and with Google's GSON. I finally got this to work but I'm not really happy with it. This is just code that works, what I want my solution to be is good code for when I work with JSON again in the future - I imagine this is inefficient because I'm first turning it into a tree and then parsing that. Can anyone suggest improvements?

List<Book> books = new ArrayList<Book>();
JsonFactory f = new JsonFactory();
JsonParser jp = f.createJsonParser(json);
jp.setCodec(new ObjectMapper());
JsonNode node = jp.readValueAsTree();

JsonNode books = node.findValue("Books");

Iterator<JsonNode> it = books.getElements();
while(it.hasNext()){
    JsonNode temp = it.next();
    Book book = new Book();
    book.setIsbn(temp.findValue("isbn").asText());
    book.setTitle(temp.findValue("title").asText());
    book.setAuthor(temp.findValue("author").asText());
    book.setPrice(temp.findValue("price").asDouble());
    books.add(book);
}

The only reason I have the setCodec line is because without it, I was getting an IllegalStateException with the message: No ObjectCodec defined for the parser, can not deserialize JSON into JsonNode tree.

From Jackson API , I had tried using the Streaming API method. But I had to call jp.nextToken() about 10 times just to get to the first isbn value and it looked very messy. Although the API does say its 20% / 30% faster.

Appreciate any feedback on this.

You can write your own Deserializer in Gson: https://sites.google.com/site/gson/gson-user-guide#TOC-Writing-a-Deserializer

Or you can create an Object with the needed setters and let Gson do the work: http://java.dzone.com/articles/deserializing-json-java-object

Some JSON libraries that are available are more or less cumbersome. In my opinion traversing a tree is one of those things you should not care about when using an external library. In that case you could even write it on your own.

But there is one nice library I found a time ago which does all this work for you, called json-io . The disadvantage of this library is that it doesn't work with Android very well. (I got OutOfMemory exceptions on Android.)

Jackson provides three "levels" of parsing: the streaming API is the foundational layer, and parsing JSON to tree nodes or your own classes is provided on top of that. Although using the streaming API directly may be most efficient, it requires a lot of labour, as you found.

In fact most of what you need seems to be in the 1-minute tutorial , no need to jump ahead!

Rather than parsing to JsonNode, Jackson can create/populate your own Book objects directly. Since you already have bean-style setters this should be a simple drop-in.

(BTW typical usage is to create an ObjectMapper initially-- typically as a singleton-- and create JsonFactory, ObjectReader etc from it rather than the other way round)

So you could read a single Book object like so:

Book book = mapper.readValue("{\"isbn\":\"1849830347\"}", Book.class);

You can create some sort of wrapper object to represent the object under the "Search" key:

/*static*/ class SearchResult {
    @JsonProperty("Books")
    public List<Book> books;
}
SearchResult result = mapper.readValue("{\"Books\":[{\"isbn\":\"...\"}]}", SearchResult.class);
List<Book> books = result.books;

( @JsonProperty needed to specify an uppercase "Books" field name, as opposed to the default. Shown as a public field for brevity, a private field with getter and setter works too)

And then you could add another layer of object to represent the entire message, or add a @JsonRootName annotation to tell Jackson to do an extra level of unwrapping when SearchResult is the "root" type:

@JsonRootName(value="Search")
class SearchResult { ... }

A lot of the time you can avoid using JsonNode, JsonFactory etc directly with Jackson. If you only annotate classes for property names etc., then you have very little else to do to use the classes symmetrically for parsing and formatting.

I have used Jackson in many projects, and will use it again in the future without hesitation.

In the majority of cases I like to develop java POJO's that model the JSON message. so in your case you would need a Book POJO to model this inner part of your JSON

{
     "isbn": "1849830347",
     "title": "American Assassin",
     "author": "Vince Flynn",
     "price": 7.99
 }

the matching POJO would be this

public class BookVO {

    private final String isbn;
    private final String title;
    private final String author;
    private final double price;

    @JsonCreator
    public BookVO(@JsonProperty("isbn") final String isbn, @JsonProperty("title") final String title, @JsonProperty("author") final String author, @JsonProperty("price") final double price) {
        super();
        this.isbn = isbn;
        this.title = title;
        this.author = author;
        this.price = price;
    }

    public String getIsbn() {
        return isbn;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public double getPrice() {
        return price;
    }

}

The parent POJO for the book above is this

public class BookSearchVO {

    private final BookVO[] books;

    @JsonCreator
    public BookSearchVO(@JsonProperty("Books") final BookVO[] books) {
        super();
        this.books = books;
    }

    public BookVO[] getBooks() {
        return books;
    }

}

the Grand Parent POJO is this

public class SearchVO {

    private final BookSearchVO search;

    @JsonCreator
    public SearchVO(@JsonProperty("Search") final BookSearchVO search) {
        super();
        this.search = search;
    }

    public BookSearchVO getSearch() {
        return search;
    }

}

The to convert the JSON into Java objects simply do this

final ObjectMapper mapperBook = new ObjectMapper();
final SearchVO results = mapperBook.readValue(new File("books.json"), SearchVO.class);

The content of books.json is

{
   "Search":{
      "Books":[
         {
            "isbn":"1849830347",
            "title":"American Assassin",
            "author":"Vince Flynn",
            "price":7.99
         },
         {
            "isbn":"0857208683",
            "title":"Kill Shot",
            "author":"Vince Flynn",
            "price":5.99
         }
      ]
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM