8

I have a fairly large JSON response in which I'm interested in single field - status:

{
  "title": "Some title",
  "status": "pending",
  "data": {
    ...
  },
  "meta": {
    ...
  }
}

All I need to do is read the status value of the JSON response as string. I would prefer to not have to build a POJO to model it, because in my application I just need to store the JSON in a database on a particular status or discard it.

The application already uses Jackson for other more complicated cases so I'd prefer to stick with that library. So far all the examples I've found try to map the JSON to an object.

ddinchev
  • 33,683
  • 28
  • 88
  • 133
  • Is `"status"` always the third line/second key-value pair in the response? – Jonny Henly Aug 03 '16 at 01:23
  • I have just posted an example formatted response. It probably comes gzipped from the API, the server decompresses it and at the point I can access it, it's just a `Byte[]` stream that I can decode as UTF8 string - no assumptions can be made besides that it's valid JSON... – ddinchev Aug 03 '16 at 01:26

2 Answers2

15

If the json response is not very large, you can use ObjectMapper.readTree to deserialize the full json into JsonNode and get a particular property in it.

For example:

ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(json);
JsonNode statusNode = jsonNode.get("status");
String statusValue = statusNode.textValue();

You can also deserialize the full json into ObjectNode by ObjectMapper.readValue suggested in other answer.

If the json response is huge and deserialize the full json is a concern, you may consider to use the Jackson Streaming API, JsonParser, to parse the json.

Following is an example:

String statusValue = null;

JsonFactory jsonFactory = new JsonFactory();
try (JsonParser parser = jsonFactory.createParser(json)) {
    JsonToken startToken = parser.nextToken();
    while (parser.nextToken() != JsonToken.END_OBJECT) {
        String fieldName = parser.getCurrentName();
        if ("status".equals(fieldName)) {
            if (parser.nextToken() == JsonToken.VALUE_STRING) {
                statusValue = parser.getValueAsString();
                break;
            }
        } else {
            parser.skipChildren();
        }
    }
}

JsonParser break down the json into a sequence of tokens which you can iterate one by one. In the example, JsonParser.skipChildren is used to skip over a complete object or an array such that only the first level of json hierarchy is checked.

Community
  • 1
  • 1
Wilson
  • 11,339
  • 2
  • 29
  • 33
  • Thank you for the suggestion, upvoted and I'm actually trying this out now! I've accepted the other answer because it's more straight to the point given my question. – ddinchev Aug 03 '16 at 03:01
  • Happy to help. You can try both way and find the one most suitable for your case. – Wilson Aug 03 '16 at 03:39
  • The use case presented in the question does not have memory issues, so the full deserialization approach (via `JsonNode`) can be used. Good point about larger sizes and streaming. +1 to both the question and the answer. :-) – PNS Aug 03 '16 at 14:36
  • @PNS Thanks for your comment and to mention the streaming approach in your answer:) I believe this can help others to think about different approach when face a similar issue. – Wilson Aug 03 '16 at 15:22
  • Exactly. That is the main point of having `StackOverflow`. I also mainly use streaming with `Jackson`, instead of POJOs or the `JsonNode` approach. :-) – PNS Aug 03 '16 at 19:40
8

If the field required is a non-null text field, at the "first level" of the hierarchy (i.e., not any nested object) of a JSON object small enough to fit in main memory, a simple way of retrieving its value is using a method like

  public static String readField(String json, String name) throws IOException {
    if (field != null) {
      ObjectNode object = new ObjectMapper().readValue(json, ObjectNode.class);
      JsonNode node = object.get(name);
      return (node == null ? null : node.textValue());
    }
    return null;
  }

ObjectNode is a generic Jackson class, not a POJO. If multiple values are to be used, the ObjectMapper should be cached (it is even thread-safe).

Running

System.out.println(readField(response, "status"));

using the JSON response string above, returns

pending

as expected. A similar solution can be found elsewhere in StackOverflow.

For very large JSON objects (e.g., stored in files), the streaming approach of Jackson should be used, as suggested in other answers.

Community
  • 1
  • 1
PNS
  • 19,295
  • 32
  • 96
  • 143