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

请记住,JSON结构之前是未知的,即它完全是任意的,我们只知道它是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" } ] } } } 

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

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

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

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

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

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

虽然我需要使用“点符号”使用展平键进行平面地图,如上所述。

我宁愿不自己实现这个,虽然此刻我没有看到任何其他方式……

您可以执行此操作来遍历树并跟踪您有多深,以找出点符号属性名称:

 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 map = new HashMap(); try { addKeys("", new ObjectMapper().readTree(json), map); } catch (IOException e) { e.printStackTrace(); } System.out.println(map); } private void addKeys(String currentPath, JsonNode jsonNode, Map map) { if (jsonNode.isObject()) { ObjectNode objectNode = (ObjectNode) jsonNode; Iterator> iter = objectNode.fields(); String pathPrefix = currentPath.isEmpty() ? "" : currentPath + "."; while (iter.hasNext()) { Map.Entry 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()); } } } 

它产生以下地图:

 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 

它应该很容易在属性名称中删除@$ ,尽管你可能最终会在键名中发生冲突,因为你说JSON是任意的。

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

顺便说一下,我是这个lib的作者。

 String flattenedJson = JsonFlattener.flatten(yourJson); Map 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" } 

那个怎么样:

 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 flatmap = new HashMap(); public Map parse(String value) { iterableCrawl("", null, (gson.fromJson(value, flatmap.getClass())).entrySet()); return flatmap; } private  void iterableCrawl(String prefix, String suffix, Iterable iterable) { int key = 0; for (T t : iterable) { if (suffix!=null) crawl(t, prefix+(key++)+suffix); else crawl(((Entry) t).getValue(), prefix+((Entry) t).getKey()); } } private void crawl(Object object, String key) { if (object instanceof ArrayList) iterableCrawl(key+"[", "]", (ArrayList)object); else if (object instanceof Map) iterableCrawl(key+".", null, ((Map)object).entrySet()); else flatmap.put(key, object.toString()); } } 

您可以使用Typesafe配置库实现类似的function,如以下示例所示:

 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 e : cfg.entrySet()) { System.out.println(e); } } } 

Spring Integration的 org.springframework.integration.transformer.ObjectToMapTransformer产生了所需的结果。 默认情况下,它将shouldFlattenKeys属性设置为true并生成平面贴图(无嵌套,值始终为简单类型)。 当shouldFlattenKeys=false它会生成嵌套映射

ObjectToMapTransformer旨在用作集成流程的一部分,但以独立方式使用它是完全正确的。 您需要使用转换输入的有效负载构造org.springframework.messaging.Messagetransform方法返回带有效值为Map的org.springframework.messaging.Message对象

 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 payload = (Map) transformer .transform(message) .getPayload(); 

旁注:将Spring Integration添加到类路径只是为了使用单个类可能有点过头了,但是你可以检查这个类的实现并自己编写类似的解决方案。 嵌套映射由Jackson( org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class) )生成,然后mapis以递归方式处理,展平所有集合的值。

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

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

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

除非你想要这样的东西:

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

如果是这样,那么遍历你的地图并构建一个“扁平”地图似乎并不是一个太大的问题。

或者是别的什么?