Je demande des données à un serveur qui renvoie des données au format JSON. Utiliser une HashMap dans JSON lors de la création de la requête n'a pas été difficile, mais l'inverse semble être un peu délicat. La réponse JSON ressemble à ceci:
{
"header" : {
"alerts" : [
{
"AlertID" : "2",
"TSExpires" : null,
"Target" : "1",
"Text" : "woot",
"Type" : "1"
},
{
"AlertID" : "3",
"TSExpires" : null,
"Target" : "1",
"Text" : "woot",
"Type" : "1"
}
],
"session" : "0bc8d0835f93ac3ebbf11560b2c5be9a"
},
"result" : "4be26bc400d3c"
}
Quel serait le moyen le plus facile d'accéder à ces données? J'utilise le module GSON.
Voici:
import Java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
Type type = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> myMap = gson.fromJson("{'k1':'Apple','k2':'orange'}", type);
Ce code fonctionne:
Gson gson = new Gson();
String json = "{\"k1\":\"v1\",\"k2\":\"v2\"}";
Map<String,Object> map = new HashMap<String,Object>();
map = (Map<String,Object>) gson.fromJson(json, map.getClass());
Je sais que la question est assez ancienne, mais je recherchais une solution permettant de désérialiser de manière générique le code JSON imbriqué en un Map<String, Object>
, sans rien trouver.
De la manière dont mon désérialisateur yaml fonctionne, les objets JSON par défaut sont définis sur Map<String, Object>
lorsque vous ne spécifiez pas de type, mais que gson ne semble pas le faire. Heureusement, vous pouvez le réaliser avec un désérialiseur personnalisé.
J'ai utilisé le désérialiseur suivant pour désérialiser naturellement quoi que ce soit, par défaut de JsonObject
s à Map<String, Object>
et de JsonArray
s à Object[]
s, où tous les enfants sont désérialisés de la même manière.
private static class NaturalDeserializer implements JsonDeserializer<Object> {
public Object deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) {
if(json.isJsonNull()) return null;
else if(json.isJsonPrimitive()) return handlePrimitive(json.getAsJsonPrimitive());
else if(json.isJsonArray()) return handleArray(json.getAsJsonArray(), context);
else return handleObject(json.getAsJsonObject(), context);
}
private Object handlePrimitive(JsonPrimitive json) {
if(json.isBoolean())
return json.getAsBoolean();
else if(json.isString())
return json.getAsString();
else {
BigDecimal bigDec = json.getAsBigDecimal();
// Find out if it is an int type
try {
bigDec.toBigIntegerExact();
try { return bigDec.intValueExact(); }
catch(ArithmeticException e) {}
return bigDec.longValue();
} catch(ArithmeticException e) {}
// Just return it as a double
return bigDec.doubleValue();
}
}
private Object handleArray(JsonArray json, JsonDeserializationContext context) {
Object[] array = new Object[json.size()];
for(int i = 0; i < array.length; i++)
array[i] = context.deserialize(json.get(i), Object.class);
return array;
}
private Object handleObject(JsonObject json, JsonDeserializationContext context) {
Map<String, Object> map = new HashMap<String, Object>();
for(Map.Entry<String, JsonElement> entry : json.entrySet())
map.put(entry.getKey(), context.deserialize(entry.getValue(), Object.class));
return map;
}
}
Le désordre dans la méthode handlePrimitive
est de s'assurer que vous n'obtenez jamais qu'un double, un entier ou un long, et pourrait probablement être meilleur, ou du moins simplifié si vous voulez bien avoir BigDecimals, ce qui, à mon avis, est le défaut.
Vous pouvez enregistrer cet adaptateur comme suit:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
Gson gson = gsonBuilder.create();
Et puis appelez comme ça:
Object natural = gson.fromJson(source, Object.class);
Je ne sais pas pourquoi ce n'est pas le comportement par défaut de gson, car c'est le cas dans la plupart des autres bibliothèques de sérialisation semi-structurées ...
Avec Gson 2.7 de Google (probablement aussi des versions antérieures, mais j'ai testé avec la version actuelle 2.7), c'est aussi simple que:
Map map = gson.fromJson(jsonString, Map.class);
Ce qui retourne une Map
de type com.google.gson.internal.LinkedTreeMap
et fonctionne de manière récursive sur des objets imbriqués, des tableaux, etc.
J'ai exécuté l'exemple OP comme suit (simplement remplacé par des guillemets simples et des espaces supprimés):
String jsonString = "{'header': {'alerts': [{'AlertID': '2', 'TSExpires': null, 'Target': '1', 'Text': 'woot', 'Type': '1'}, {'AlertID': '3', 'TSExpires': null, 'Target': '1', 'Text': 'woot', 'Type': '1'}], 'session': '0bc8d0835f93ac3ebbf11560b2c5be9a'}, 'result': '4be26bc400d3c'}";
Map map = gson.fromJson(jsonString, Map.class);
System.out.println(map.getClass().toString());
System.out.println(map);
Et obtenu la sortie suivante:
class com.google.gson.internal.LinkedTreeMap
{header={alerts=[{AlertID=2, TSExpires=null, Target=1, Text=woot, Type=1}, {AlertID=3, TSExpires=null, Target=1, Text=woot, Type=1}], session=0bc8d0835f93ac3ebbf11560b2c5be9a}, result=4be26bc400d3c}
Mise à jour pour la nouvelle librairie Gson:
Vous pouvez maintenant analyser directement Json imbriqué sur Mapper, mais vous devez savoir si vous essayez d'analyser Json avec le type Map<String, Object>
: il déclenchera une exception. Pour résoudre ce problème, déclarez simplement le résultat en tant que type LinkedTreeMap
. Exemple ci-dessous:
String nestedJSON = "{"id":"1","message":"web_didload","content":{"success":1}};
Gson gson = new Gson();
LinkedTreeMap result = gson.fromJson(nestedJSON , LinkedTreeMap.class);
J'avais exactement la même question et je me suis retrouvé ici. J'ai eu une approche différente qui semble beaucoup plus simple (peut-être de nouvelles versions de gson?).
Gson gson = new Gson();
Map jsonObject = (Map) gson.fromJson(data, Object.class);
avec le json suivant
{
"map-00": {
"array-00": [
"entry-00",
"entry-01"
],
"value": "entry-02"
}
}
Le suivant
Map map00 = (Map) jsonObject.get("map-00");
List array00 = (List) map00.get("array-00");
String value = (String) map00.get("value");
for (int i = 0; i < array00.size(); i++) {
System.out.println("map-00.array-00[" + i + "]= " + array00.get(i));
}
System.out.println("map-00.value = " + value);
les sorties
map-00.array-00[0]= entry-00
map-00.array-00[1]= entry-01
map-00.value = entry-02
Vous pouvez vérifier dynamiquement en utilisant instanceof lors de la navigation de votre jsonObject. Quelque chose comme
Map json = gson.fromJson(data, Object.class);
if(json.get("field") instanceof Map) {
Map field = (Map)json.get("field");
} else if (json.get("field") instanceof List) {
List field = (List)json.get("field");
} ...
Cela fonctionne pour moi, donc cela doit fonctionner pour vous ;-)
Essayez ceci, cela fonctionnera. Je l'ai utilisé pourHashtable.
public static Hashtable<Integer, KioskStatusResource> parseModifued(String json) {
JsonObject object = (JsonObject) new com.google.gson.JsonParser().parse(json);
Set<Map.Entry<String, JsonElement>> set = object.entrySet();
Iterator<Map.Entry<String, JsonElement>> iterator = set.iterator();
Hashtable<Integer, KioskStatusResource> map = new Hashtable<Integer, KioskStatusResource>();
while (iterator.hasNext()) {
Map.Entry<String, JsonElement> entry = iterator.next();
Integer key = Integer.parseInt(entry.getKey());
KioskStatusResource value = new Gson().fromJson(entry.getValue(), KioskStatusResource.class);
if (value != null) {
map.put(key, value);
}
}
return map;
}
RemplacezKioskStatusResourcepar votre classe etIntegerpar votre classe de clés.
Ci-dessous est pris en charge depuis gson 2.8.0
public static Type getMapType(Class keyType, Class valueType){
return TypeToken.getParameterized(HashMap.class, keyType, valueType).getType();
}
public static <K,V> HashMap<K,V> fromMap(String json, Class<K> keyType, Class<V> valueType){
return gson.fromJson(json, getMapType(keyType,valueType));
}
Voici ce que j'ai utilisé:
public static HashMap<String, Object> parse(String json) {
JsonObject object = (JsonObject) parser.parse(json);
Set<Map.Entry<String, JsonElement>> set = object.entrySet();
Iterator<Map.Entry<String, JsonElement>> iterator = set.iterator();
HashMap<String, Object> map = new HashMap<String, Object>();
while (iterator.hasNext()) {
Map.Entry<String, JsonElement> entry = iterator.next();
String key = entry.getKey();
JsonElement value = entry.getValue();
if (!value.isJsonPrimitive()) {
map.put(key, parse(value.toString()));
} else {
map.put(key, value.getAsString());
}
}
return map;
}
Vous pouvez utiliser cette classe à la place :) (gère même les listes, les listes imbriquées et json)
public class Utility {
public static Map<String, Object> jsonToMap(Object json) throws JSONException {
if(json instanceof JSONObject)
return _jsonToMap_((JSONObject)json) ;
else if (json instanceof String)
{
JSONObject jsonObject = new JSONObject((String)json) ;
return _jsonToMap_(jsonObject) ;
}
return null ;
}
private static Map<String, Object> _jsonToMap_(JSONObject json) throws JSONException {
Map<String, Object> retMap = new HashMap<String, Object>();
if(json != JSONObject.NULL) {
retMap = toMap(json);
}
return retMap;
}
private static Map<String, Object> toMap(JSONObject object) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
Iterator<String> keysItr = object.keys();
while(keysItr.hasNext()) {
String key = keysItr.next();
Object value = object.get(key);
if(value instanceof JSONArray) {
value = toList((JSONArray) value);
}
else if(value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
map.put(key, value);
}
return map;
}
public static List<Object> toList(JSONArray array) throws JSONException {
List<Object> list = new ArrayList<Object>();
for(int i = 0; i < array.length(); i++) {
Object value = array.get(i);
if(value instanceof JSONArray) {
value = toList((JSONArray) value);
}
else if(value instanceof JSONObject) {
value = toMap((JSONObject) value);
}
list.add(value);
}
return list;
}
}
Pour convertir votre chaîne JSON en hashmap utilisez ceci:
HashMap<String, Object> hashMap = new HashMap<>(Utility.jsonToMap(response)) ;
J'ai surmonté un problème similaire avec un JsonDeSerializer personnalisé. J'ai essayé de le rendre un peu générique mais toujours pas assez. C'est une solution qui répond à mes besoins.
Tout d'abord, vous devez implémenter un nouveau JsonDeserializer pour les objets Map.
public class MapDeserializer<T, U> implements JsonDeserializer<Map<T, U>>
Et la méthode de désérialisation ressemblera à ceci:
public Map<T, U> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
if (!json.isJsonObject()) {
return null;
}
JsonObject jsonObject = json.getAsJsonObject();
Set<Entry<String, JsonElement>> jsonEntrySet = jsonObject.entrySet();
Map<T, U> deserializedMap = new HashMap<T, U>();
for (Entry<Java.lang.String, JsonElement> entry : jsonEntrySet) {
try {
U value = context.deserialize(entry.getValue(), getMyType());
deserializedMap.put((T) entry.getKey(), value);
} catch (Exception ex) {
logger.info("Could not deserialize map.", ex);
}
}
return deserializedMap;
}
Le problème avec cette solution est que la clé de ma carte est toujours du type "String". Cependant, en changeant certaines choses, quelqu'un peut le rendre générique. De plus, je dois dire que la classe de la valeur doit être passée dans le constructeur. Donc, la méthode getMyType()
dans mon code renvoie le type des valeurs de la carte, qui a été passé dans le constructeur.
Vous pouvez référencer ce post Comment puis-je écrire un désérialiseur JSON personnalisé pour Gson? pour en savoir plus sur les désérialiseurs personnalisés.
Voici un one-liner qui le fera:
HashMap<String, Object> myMap =
gson.fromJson(yourJson, new TypeToken<HashMap<String, Object>>(){}.getType());
C’est plus un addendum à la réponse de Kevin Dolan qu’une réponse complète, mais j’avais du mal à extraire le type du numéro. Ceci est ma solution:
private Object handlePrimitive(JsonPrimitive json) {
if(json.isBoolean()) {
return json.getAsBoolean();
} else if(json.isString())
return json.getAsString();
}
Number num = element.getAsNumber();
if(num instanceof Integer){
map.put(fieldName, num.intValue());
} else if(num instanceof Long){
map.put(fieldName, num.longValue());
} else if(num instanceof Float){
map.put(fieldName, num.floatValue());
} else { // Double
map.put(fieldName, num.doubleValue());
}
}