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...
Flatten a JSON object: var flatten = (function (isArray, wrapped) { return function (table) { return reduce("", {}, table); }; function reduce(path, accumulator, table) { if (isArray(table)) { var length = table.
Use the Flatten JSON Objects extension to convert a nested data layer object into a new object with only one layer of key/value pairs.
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.
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"
}
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());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With