Java 如何将 JSON 反序列化为扁平的类似 Map 的结构?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20355261/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 01:18:53  来源:igfitidea点击:

How to deserialize JSON into flat, Map-like structure?

javajsonHymansonflatten

提问by Svilen

Have in mind that the JSON structure is not known before hand i.e. it is completely arbitrary, we only know that it is JSON format.

请记住,JSON 结构是事先不知道的,即它是完全任意的,我们只知道它是 JSON 格式。

For example,

例如,

The following JSON

以下 JSON

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

Should be deserialized into Map-like structure having keys like (not all of the above included for brevity):

应该反序列化为具有类似键的类似 Map 的结构(为简洁起见,不包括上述所有内容):

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

I am looking into Hymanson currently, so there we have:

我目前正在调查Hyman逊,所以我们有:

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

However, the resulting Map instance is basically a Map of nested Maps:

但是,生成的 Map 实例基本上是嵌套 Map 的 Map:

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

While I need flat Map with flatten keys using "dot notation", like the above.

虽然我需要使用“点表示法”使用扁平键的平面地图,如上所示。

I would rather not implement this myself, although at the moment I don't see any other way...

我宁愿自己不实现这一点,尽管目前我看不到任何其他方式......

采纳答案by Harleen

You can do this to traverse the tree and keep track of how deep you are to figure out dot notation property names:

您可以这样做来遍历树并跟踪您找出点符号属性名称的深度:

import com.fasterxml.Hymanson.databind.JsonNode;
import com.fasterxml.Hymanson.databind.ObjectMapper;
import com.fasterxml.Hymanson.databind.node.ArrayNode;
import com.fasterxml.Hymanson.databind.node.ObjectNode;
import com.fasterxml.Hymanson.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());
    }
  }
}

It produces the following map:

它生成以下地图:

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

It should be easy enough to strip out @and $in the property names, although you could end up with collisions in key names since you said the JSON was arbitrary.

去除@$属性名称应该很容易,尽管由于您说 JSON 是任意的,因此最终可能会在键名称中发生冲突。

回答by siledh

If you know the structure beforehand, you can define a Java class and use gsonto parse JSON into an instance of that class:

如果您事先知道结构,则可以定义一个 Java 类并使用gson将 JSON 解析为该类的实例:

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

If not, then I'm not sure what you're trying to do. You obviously can't define a class on-the-fly so accessing the parsed JSON using dot-notation is out of the question.

如果没有,那么我不确定您要做什么。您显然无法即时定义类,因此使用点符号访问解析的 JSON 是不可能的。

Unless you want something like:

除非你想要这样的东西:

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

If so, then traversing your map of maps and building a "flattened" map doesn't seem too much of a problem.

如果是这样,那么遍历您的地图地图并构建“扁平化”地图似乎并没有太大问题。

Or is it something else?

或者是别的什么?

回答by Giovanni Botta

You can achieve something like that using the Typesafe Config Libraryas in the following example:

您可以使用Typesafe Config Library实现类似的功能,如下例所示:

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

回答by Amnons

how about that:

那个怎么样:

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

回答by user3360932

How about using the json-flattener. https://github.com/wnameless/json-flattener

如何使用 json-flattener。https://github.com/wnameless/json-flattener

BTW, I am the author of this 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"
}

回答by Bartosz Bilicki

org.springframework.integration.transformer.ObjectToMapTransformerfrom Spring Integrationproduces desired result. By default it has shouldFlattenKeysproperty set to true and produces flat maps (no nesting, value is always simple type). When shouldFlattenKeys=falseit produces nested maps

org.springframework.integration.transformer.ObjectToMapTransformerSpring集成产生期望的结果。默认情况下,它的shouldFlattenKeys属性设置为 true 并生成平面地图(无嵌套,值始终是简单类型)。当shouldFlattenKeys=false它生成嵌套映射时

ObjectToMapTransformer is meant to be used as part of integration flow, but it is perfectly fine to use it in stand-alone way. You need to construct org.springframework.messaging.Messagewith payload of transformation input. transformmethod returns org.springframework.messaging.Messageobject with payload that is Map

ObjectToMapTransformer 旨在用作集成流程的一部分,但以独立方式使用它完全没问题。您需要org.springframework.messaging.Message使用转换输入的有效负载来构建。transform方法返回org.springframework.messaging.Message有效负载为 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();

Side note: It is probably overkill to add Spring Integration to the classpath just to use single class, but you may check implementation of this class and write similar solution on your own. Nested map is produced by Hymanson (org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class)), then mapis travered recursively, flattening all values that are collections.

旁注:仅仅为了使用单个类而将 Spring Integration 添加到类路径可能是过度的,但您可以检查此类的实现并自行编写类似的解决方案。嵌套映射由 Hymanson ( org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class)) 生成,然后递归遍历映射,将所有属于集合的值展平。

回答by wangyuhong

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;

import java.util.Iterator;
import java.util.Map;

public class JsonMapFlattener {


    public static Map<String, Object> flatten(Map<String, ? extends Object> inputMap) {
        return org.springframework.vault.support.JsonMapFlattener.flatten(inputMap);
    }


    public static JSONObject unflatten(Map<String, ? extends Object> inputMap) {
        Map<String, Object> map = flatten(inputMap);
        Iterator<String> it = map.keySet().iterator();
        JSONObject jo2 = new JSONObject();
        while (it.hasNext()) {
            String key = it.next();
            Object value = map.get(key);
            JSONPath.set(jo2, key, value);
        }
        return jo2;

    }
}