可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
For example,
The following 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):
port[0].alias
port[0].enabled
port[0].extendedProperties.connectionTimeout
port[0].threadPool.max
I am looking into Jackson currently, so there we have:
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:
{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...
回答1:
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.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());
}
}
}
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.
回答2:
How about using the 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"
}
回答3:
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());
}
}
回答4:
You can achieve something like that using the Typesafe Config Library as in the following example:
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);
}
}
}
回答5:
org.springframework.integration.transformer.ObjectToMapTransformer from Spring Integration produces desired result.
By default it has shouldFlattenKeys
property set to true and produces flat maps (no nesting, value is always simple type). When shouldFlattenKeys=false
it produces nested maps
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.Message
with payload of transformation input. transform
method returns org.springframework.messaging.Message
object with payload that is 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 Jackson (org.springframework.integration.support.json.JsonObjectMapper#fromJson(payload, Map.class)
), then mapis travered recursively, flattening all values that are collections.
回答6:
If you know the structure beforehand, you can define a Java class and use gson to parse JSON into an instance of that class:
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.
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?