web-dev-qa-db-fra.com

Comment désérialiser JSON en une structure plate ressemblant à une carte?

N'oubliez pas que la structure JSON n'est pas connue auparavant, c'est-à-dire qu'elle est complètement arbitraire. Nous savons seulement qu'il s'agit du format JSON.

Par exemple,

Le JSON suivant

{
   "Port":
   {
       "@alias": "defaultHttp",
       "Enabled": "true",
       "Number": "10092",
       "Protocol": "http",
       "KeepAliveTimeout": "20000",
       "ThreadPool":
       {
           "@enabled": "false",
           "Max": "150",
           "ThreadPriority": "5"
       },
       "ExtendedProperties":
       {
           "Property":
           [                         
               {
                   "@name": "connectionTimeout",
                   "$": "20000"
               }
           ]
       }
   }
}

Doit être désérialisé en une structure ressemblant à celle de la carte avec des clés comme celle-ci (toutes les réponses ci-dessus ne sont pas incluses pour des raisons de concision):

port[0].alias
port[0].enabled
port[0].extendedProperties.connectionTimeout
port[0].threadPool.max

Je suis en train de regarder Jackson, alors nous avons:

TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};
Map<String, String> o = objectMapper.readValue(jsonString, typeRef);

Cependant, l'instance de carte résultante est essentiellement une carte de cartes imbriquées:

{Port={@alias=diagnostics, Enabled=false, Type=DIAGNOSTIC, Number=10033, Protocol=JDWP, ExtendedProperties={Property={@name=suspend, $=n}}}}

Bien que j'ai besoin d'une carte plate avec des touches plates en utilisant la "notation par points", comme ci-dessus.

Je préférerais ne pas l'appliquer moi-même, bien que pour le moment je ne vois pas d'autre moyen ...

11
Svilen

Vous pouvez le faire pour parcourir l’arbre et garder une trace de la profondeur à laquelle vous devez déterminer les noms de propriété de la notation par points:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ValueNode;
import Java.io.IOException;
import Java.util.HashMap;
import Java.util.Iterator;
import Java.util.Map;
import org.junit.Test;

public class FlattenJson {
  String json = "{\n" +
      "   \"Port\":\n" +
      "   {\n" +
      "       \"@alias\": \"defaultHttp\",\n" +
      "       \"Enabled\": \"true\",\n" +
      "       \"Number\": \"10092\",\n" +
      "       \"Protocol\": \"http\",\n" +
      "       \"KeepAliveTimeout\": \"20000\",\n" +
      "       \"ThreadPool\":\n" +
      "       {\n" +
      "           \"@enabled\": \"false\",\n" +
      "           \"Max\": \"150\",\n" +
      "           \"ThreadPriority\": \"5\"\n" +
      "       },\n" +
      "       \"ExtendedProperties\":\n" +
      "       {\n" +
      "           \"Property\":\n" +
      "           [                         \n" +
      "               {\n" +
      "                   \"@name\": \"connectionTimeout\",\n" +
      "                   \"$\": \"20000\"\n" +
      "               }\n" +
      "           ]\n" +
      "       }\n" +
      "   }\n" +
      "}";

  @Test
  public void testCreatingKeyValues() {
    Map<String, String> map = new HashMap<String, String>();
    try {
      addKeys("", new ObjectMapper().readTree(json), map);
    } catch (IOException e) {
      e.printStackTrace();
    }
    System.out.println(map);
  }

  private void addKeys(String currentPath, JsonNode jsonNode, Map<String, String> map) {
    if (jsonNode.isObject()) {
      ObjectNode objectNode = (ObjectNode) jsonNode;
      Iterator<Map.Entry<String, JsonNode>> iter = objectNode.fields();
      String pathPrefix = currentPath.isEmpty() ? "" : currentPath + ".";

      while (iter.hasNext()) {
        Map.Entry<String, JsonNode> entry = iter.next();
        addKeys(pathPrefix + entry.getKey(), entry.getValue(), map);
      }
    } else if (jsonNode.isArray()) {
      ArrayNode arrayNode = (ArrayNode) jsonNode;
      for (int i = 0; i < arrayNode.size(); i++) {
        addKeys(currentPath + "[" + i + "]", arrayNode.get(i), map);
      }
    } else if (jsonNode.isValueNode()) {
      ValueNode valueNode = (ValueNode) jsonNode;
      map.put(currentPath, valueNode.asText());
    }
  }
}

Il produit la carte suivante:

Port.ThreadPool.Max=150, 
Port.ThreadPool.@enabled=false, 
Port.Number=10092, 
Port.ExtendedProperties.Property[0].@name=connectionTimeout, 
Port.ThreadPool.ThreadPriority=5, 
Port.Protocol=http, 
Port.KeepAliveTimeout=20000, 
Port.ExtendedProperties.Property[0].$=20000, 
Port.@alias=defaultHttp, 
Port.Enabled=true

Il devrait être assez facile de supprimer @ et $ dans les noms de propriété, bien que vous puissiez vous retrouver avec des collisions dans les noms de clé puisque vous avez dit que le JSON était arbitraire. 

28
Harleen

Que diriez-vous d'utiliser le Json-Flattener. https://github.com/wnameless/json-flattener

BTW, je suis l'auteur de cette lib.

String flattenedJson = JsonFlattener.flatten(yourJson);
Map<String, Object> flattenedJsonMap = JsonFlattener.flattenAsMap(yourJson);

// Result:
{
    "Port.@alias":"defaultHttp",
    "Port.Enabled":"true",
    "Port.Number":"10092",
    "Port.Protocol":"http",
    "Port.KeepAliveTimeout":"20000",
    "Port.ThreadPool.@enabled":"false",
    "Port.ThreadPool.Max":"150",
    "Port.ThreadPool.ThreadPriority":"5",
    "Port.ExtendedProperties.Property[0].@name":"connectionTimeout",
    "Port.ExtendedProperties.Property[0].$":"20000"
}
15
user3360932

que diriez-vous de cela:

import Java.util.ArrayList;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Map.Entry;
import com.google.gson.Gson;

/**
 * NOT FOR CONCURENT USE
*/
@SuppressWarnings("unchecked")
public class JsonParser{

Gson gson=new Gson();
Map<String, String> flatmap = new HashMap<String, String>();

public Map<String, String> parse(String value) {        
    iterableCrawl("", null, (gson.fromJson(value, flatmap.getClass())).entrySet());     
    return flatmap; 
}

private <T> void iterableCrawl(String prefix, String suffix, Iterable<T> iterable) {
    int key = 0;
    for (T t : iterable) {
        if (suffix!=null)
            crawl(t, prefix+(key++)+suffix);
        else
            crawl(((Entry<String, Object>) t).getValue(), prefix+((Entry<String, Object>) t).getKey());
    }
}

private void crawl(Object object, String key) {
    if (object instanceof ArrayList)
        iterableCrawl(key+"[", "]", (ArrayList<Object>)object);
    else if (object instanceof Map)
        iterableCrawl(key+".", null, ((Map<String, Object>)object).entrySet());
    else
        flatmap.put(key, object.toString());
}
}
7
Amnons

org.springframework.integration.transformer.ObjectToMapTransformer from Spring Integration produit le résultat souhaité. Par défaut, la propriété shouldFlattenKeys est définie sur true et produit des mappes plates (aucune imbrication, la valeur est toujours simple) type). Quand shouldFlattenKeys=false, il produit des cartes imbriquées

ObjectToMapTransformer est destiné à être utilisé dans le cadre d'un flux d'intégration, mais il est tout à fait correct de l'utiliser de manière autonome. Vous devez construire org.springframework.messaging.Message avec la charge utile de l'entrée de transformation. La méthode transform renvoie l'objet org.springframework.messaging.Message avec la charge correspondant à Map.

import org.springframework.integration.transformer.ObjectToMapTransformer;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;

Message message = new GenericMessage(value);
 ObjectToMapTransformer transformer = new ObjectToMapTransformer();
        transformer.setShouldFlattenKeys(true);
        Map<String,Object> payload = (Map<String, Object>) transformer
                .transform(message)
                .getPayload();

Note latérale: Il est probablement excessif d’ajouter Spring Integration au classpath pour utiliser une seule classe, mais vous pouvez vérifier son implémentation et écrire vous-même une solution similaire. La carte imbriquée est produite par Jackson (org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class)), puis mapis est parcourue de manière récursive, en aplatissant toutes les valeurs qui sont des collections.

1
Bartosz Bilicki

Vous pouvez obtenir quelque chose comme ça en utilisant Typesafe Config Library comme dans l'exemple suivant:

import com.typesafe.config.*;
import Java.util.Map;
public class TypesafeConfigExample {
  public static void main(String[] args) {
    Config cfg = ConfigFactory.parseString(
      "   \"Port\":\n" +
      "   {\n" +
      "       \"@alias\": \"defaultHttp\",\n" +
      "       \"Enabled\": \"true\",\n" +
      "       \"Number\": \"10092\",\n" +
      "       \"Protocol\": \"http\",\n" +
      "       \"KeepAliveTimeout\": \"20000\",\n" +
      "       \"ThreadPool\":\n" +
      "       {\n" +
      "           \"@enabled\": \"false\",\n" +
      "           \"Max\": \"150\",\n" +
      "           \"ThreadPriority\": \"5\"\n" +
      "       },\n" +
      "       \"ExtendedProperties\":\n" +
      "       {\n" +
      "           \"Property\":\n" +
      "           [                         \n" +
      "               {\n" +
      "                   \"@name\": \"connectionTimeout\",\n" +
      "                   \"$\": \"20000\"\n" +
      "               }\n" +
      "           ]\n" +
      "       }\n" +
      "   }\n" +
      "}");

    // each key has a similar form to what you need
    for (Map.Entry<String, ConfigValue> e : cfg.entrySet()) {
      System.out.println(e);
    }
  }
}
1
Giovanni Botta

Si vous connaissez la structure au préalable, vous pouvez définir une classe Java et utiliser gson pour analyser JSON dans une instance de cette classe:

YourClass obj = gson.fromJson(json, YourClass.class); 

Sinon, je ne suis pas sûr de ce que vous essayez de faire. Vous ne pouvez évidemment pas définir une classe à la volée, il est donc hors de question d'accéder au JSON analysé à l'aide de la notation par points.

Sauf si vous voulez quelque chose comme:

Map<String, String> parsed = magicParse(json);
parsed["Port.ThreadPool.max"]; // returns 150

Si tel est le cas, parcourir votre carte et créer une carte "aplatie" ne semble pas trop poser de problème.

Ou s'agit-il d'autre chose?

0
siledh