web-dev-qa-db-fra.com

Comment mapper efficacement un org.json.JSONObject à un POJO?

Cette question devait avoir été posée auparavant, mais je ne l'ai pas trouvée.

J'utilise une bibliothèque tierce pour récupérer des données au format JSON. La bibliothèque me propose les données sous forme de org.json.JSONObject. Je veux mapper ce JSONObject à un POJO (Plain Old Java Object) pour un accès/code plus simple.

Pour le mappage, j'utilise actuellement le ObjectMapper de la bibliothèque Jackson de cette façon:

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

À ma connaissance, le code ci-dessus peut être optimisé de manière significative, car actuellement les données de JSONObject, qui sont déjà analysées, sont à nouveau introduites dans une chaîne de sérialisation-désérialisation avec la méthode JSONObject.toString() et puis au ObjectMapper.

Je veux éviter ces deux conversions (toString() et analyse). Existe-t-il un moyen d'utiliser le JSONObject pour mapper ses données directement à un POJO?

24
Daniel S.

Puisque vous avez une représentation abstraite de certaines données JSON (un org.json.JSONObject objet) et vous prévoyez d'utiliser la bibliothèque Jackson - qui a sa propre représentation abstraite des données JSON ( com.fasterxml.jackson.databind.JsonNode ) - alors une conversion d'une représentation à l'autre vous sauverait du processus d'analyse-sérialisation-analyse. Ainsi, au lieu d'utiliser la méthode readValue qui accepte un String, vous utiliseriez cette version qui accepte un JsonParser:

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

JSON est un format très simple, il ne devrait donc pas être difficile de créer le convertJsonFormat à la main. Voici ma tentative:

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;
}

Notez que, même si le JsonNode de Jackson peut représenter certains types supplémentaires (tels que BigInteger, Decimal, etc.), ils ne sont pas nécessaires car le code ci-dessus couvre tout ce qui JSONObject peut représenter.

27
mgibsonbr

Si vous n'êtes pas à égalité avec Jackson, vous pouvez utiliser la bibliothèque google-gson pratique comme alternative. Il ne nécessite qu'un seul pot et est très simple à utiliser:

Conversion d'un Java en chaîne JSON:

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

Création d'un Java à partir d'une chaîne JSON:

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

Je ne connais pas les performances par rapport à Jackson, mais c'est difficile d'être plus simple que ça ... Gson est une bibliothèque stable et largement utilisée.

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

24
Stephane Lallemagne

Ajout d'une réponse à une ancienne question, mais ...

Jackson peut se lier aux types org.json. En général, il peut convertir entre tous les types auxquels il peut se lier, en efficacement (bien que pas réellement) sérialiser en JSON et désérialiser.

Si vous avez enregistré JsonOrgModule , vous pouvez simplement effectuer la conversion directement à partir d'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;
}

J'ai mentionné que c'est efficacement la sérialisation? C'est vrai, il sérialise l'objet d'entrée dans un TokenBuffer, qui représente un flux d'événements d'analyse JSON, mais avec moins d'impact sur la création de chaînes, etc., car il peut largement référencer les données de l'entrée. Il envoie ensuite ce flux à un désérialiseur pour produire l'objet de sortie.

Donc, c'est quelque peu similaire à la suggestion de convertir le JSONObject en JsonNode, mais beaucoup plus général. Pour savoir s'il est réellement plus efficace ou non, il faudrait le mesurer: soit vous construisez un JsonNode en tant qu'intermédiaire, soit un TokenBuffer, aucun des deux n'est sans surcharge.

16
araqnid