How to efficiently map a org.json.JSONObject to a POJO?
Asked Answered
C

4

49

I'm using a 3rd party library to retrieve data in JSON format. The library offers the data to me as a org.json.JSONObject. I want to map this JSONObject to a POJO (Plain Old Java Object) for simpler access/code.

For mapping, I currently use the ObjectMapper from the Jackson library in this way:

JSONObject jsonObject = //...
ObjectMapper mapper = new ObjectMapper();
MyPojoClass myPojo = mapper.readValue(jsonObject.toString(), MyPojoClass.class);

To my understanding, the above code can be optimized significantly, because currently the data in the JSONObject, which is already parsed, is again fed into a serialization-deserialization chain with the JSONObject.toString() method and then to the ObjectMapper.

I want to avoid these two conversions (toString() and parsing). Is there a way to use the JSONObject to map its data directly to a POJO?

Canaletto answered 12/12, 2013 at 4:7 Comment(0)
H
48

Since you have an abstract representation of some JSON data (an org.json.JSONObject object) and you're planning to use the Jackson library - that has its own abstract representation of JSON data (com.fasterxml.jackson.databind.JsonNode) - then a conversion from one representation to the other would save you from the parse-serialize-parse process. So, instead of using the readValue method that accepts a String, you'd use this version that accepts a JsonParser:

JSONObject jsonObject = //...
JsonNode jsonNode = convertJsonFormat(jsonObject);
ObjectMapper mapper = new ObjectMapper();
MyPojoClass myPojo = mapper.readValue(new TreeTraversingParser(jsonNode), MyPojoClass.class);

JSON is a very simple format, so it should not be hard to create the convertJsonFormat by hand. Here's my attempt:

static JsonNode convertJsonFormat(JSONObject json) {
    ObjectNode ret = JsonNodeFactory.instance.objectNode();

    @SuppressWarnings("unchecked")
    Iterator<String> iterator = json.keys();
    for (; iterator.hasNext();) {
        String key = iterator.next();
        Object value;
        try {
            value = json.get(key);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        if (json.isNull(key))
            ret.putNull(key);
        else if (value instanceof String)
            ret.put(key, (String) value);
        else if (value instanceof Integer)
            ret.put(key, (Integer) value);
        else if (value instanceof Long)
            ret.put(key, (Long) value);
        else if (value instanceof Double)
            ret.put(key, (Double) value);
        else if (value instanceof Boolean)
            ret.put(key, (Boolean) value);
        else if (value instanceof JSONObject)
            ret.put(key, convertJsonFormat((JSONObject) value));
        else if (value instanceof JSONArray)
            ret.put(key, convertJsonFormat((JSONArray) value));
        else
            throw new RuntimeException("not prepared for converting instance of class " + value.getClass());
    }
    return ret;
}

static JsonNode convertJsonFormat(JSONArray json) {
    ArrayNode ret = JsonNodeFactory.instance.arrayNode();
    for (int i = 0; i < json.length(); i++) {
        Object value;
        try {
            value = json.get(i);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        if (json.isNull(i))
            ret.addNull();
        else if (value instanceof String)
            ret.add((String) value);
        else if (value instanceof Integer)
            ret.add((Integer) value);
        else if (value instanceof Long)
            ret.add((Long) value);
        else if (value instanceof Double)
            ret.add((Double) value);
        else if (value instanceof Boolean)
            ret.add((Boolean) value);
        else if (value instanceof JSONObject)
            ret.add(convertJsonFormat((JSONObject) value));
        else if (value instanceof JSONArray)
            ret.add(convertJsonFormat((JSONArray) value));
        else
            throw new RuntimeException("not prepared for converting instance of class " + value.getClass());
    }
    return ret;
}

Note that, while the Jackson's JsonNode can represent some extra types (such as BigInteger, Decimal, etc) they are not necessary since the code above covers everything that JSONObject can represent.

Haily answered 20/12, 2013 at 19:43 Comment(3)
I've tried it out and it works. According to my benchmarks, it has a performance gain of about 30%. Thanks!Canaletto
BTW, convertJsonFormat wont work for else if (value instanceof JSONArray) ret.put(key, convertJsonFormat((JSONArray) value));Nosewheel
I think the link for the Github does not work anymoreSweatbox
P
36

Adding an answer to an old question, but...

Jackson can bind to/from the org.json types. In general it can convert between any types that it can bind to, by effectively (although not actually) serializing to JSON and deserializing.

If you have the JsonOrgModule registered, you can simply do the conversion straight from ObjectMapper:

@Test
public void convert_from_jsonobject() throws Exception {
    JSONObject obj = new JSONObject().put("value", 3.14);
    ObjectMapper mapper = new ObjectMapper().registerModule(new JsonOrgModule());
    PojoData data = mapper.convertValue(obj, PojoData.class);
    assertThat(data.value, equalTo(3.14));
}

@Test
public void convert_to_jsonobject() throws Exception {
    PojoData data = new PojoData();
    data.value = 3.14;
    ObjectMapper mapper = new ObjectMapper().registerModule(new JsonOrgModule());
    JSONObject obj = mapper.convertValue(data, JSONObject.class);
    assertThat(obj.getDouble("value"), equalTo(3.14));
}

public static final class PojoData {
    public double value;
}

I mentioned that this is effectively serialising? That's true, it serializes the input object into a TokenBuffer, which represents a stream of JSON parsing events, but with less impact of building strings etc., as it can largely reference data from the input. It then feeds this stream to a deserializer to produce the output object.

So, it's somewhat similar to the suggestion to convert the JSONObject to a JsonNode, but much more general. Whether it's actually more efficient or not would need measuring: either you construct a JsonNode as an intermediate or a TokenBuffer, neither way is without overhead.

Period answered 18/11, 2015 at 12:26 Comment(1)
This is the best answer on this post, thank you for sharing.Conlan
D
29

If you're not tied with Jackson, you can use the handy google-gson library as an alternative. It requires only one jar and is very simple to use:

Converting a java object into a JSON string:

  String json_string = new Gson().toJson(an_object);

Creating a java object from a JSON string:

  MyObject obj = new Gson().fromJson(a_json_string, MyObject.class);

I dont't know about performance compared to Jackson, but it's hard to be simpler than this... Gson is a stable and widely used library.

See https://code.google.com/p/google-gson/

Documentation answered 22/12, 2013 at 20:39 Comment(3)
Sorry, this is not an answer to my question. Your code also does not get around the double parsing. Moreover, code complexity is the same, no matter if you use Jackson or Gson. In Jackson, your code would read MyObject obj = new ObjectMapper().readValue(a_json_string, MyObject.class);, which is really no difference except for the length of the class name Gson vs ObjectMapper.Canaletto
It answered my question and 17 other people's question.Conlan
The question was "How to convert org.json.JSONObject -> POJO". Your answer is about "How fun to use Gson".Scriabin
R
3

More simple way by using Gson.

JSONObject jsonObject = //...

PojoObject objPojo = new Gson().fromJson(jsonObject.toString(), PojoObject.class);

This worked for me.

Roberge answered 6/12, 2019 at 7:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.