This question already has answers here:
How can I access each key and value in JSONArray
(2 answers)
Closed 7 years ago.
For example from the following json, id, items, fromNumber should be retrieved.
The json can be having n number of nesting.
{
"items": [{
"id": 633706061003,
"fromNumber": "16572307534",
"contact": {
"id": 499354453003,
"homePhone": "16572307534"
},
"records": [{
"id": 353389055003,
"result": "LA",
"recordings": [{
"id": 16427622003,
}]
}]
}],
"limit": 100,
"offset": 0,
"totalCount": 5949
}
I have implemented the below code, but in this code I have to tell the level of nesting
String prefix = "";
/*
* Root Array
*/
JsonArray rootArray = new JsonParser().parse(json).getAsJsonArray();
for (int i = 0; i < rootArray.size(); i++) {
/*
* Single object in root array while iterations. for id, properties, tags etc.
*/
JsonObject rootArrayObject = rootArray.get(i).getAsJsonObject();
Set<Map.Entry<String, JsonElement>> rootArrayObjectEntrySet = rootArrayObject.entrySet();
/*
* Getting the keys and values of RootArray Single Object
*/
for (Map.Entry<String, JsonElement> entryChild : rootArrayObjectEntrySet) {
prefix = entryChild.getKey();
/*
* Getting each object, key or array as an element
*/
JsonElement rootArrayObjElement = rootArrayObject.get(entryChild.getKey());
if(rootArrayObjElement.isJsonArray()){
/*
* Getting array's object in single object of root array. Example: tags
*/
JsonArray rootArrayObjArray = rootArrayObjElement.getAsJsonArray();
for (int j = 0; j < rootArrayObjArray.size(); j++) {
}
}else if(rootArrayObjElement.isJsonObject()){
/*
* Single object in root array
*/
JsonObject rootArrayObjObj = rootArrayObjElement.getAsJsonObject();
Set<Map.Entry<String, JsonElement>> rootArrayObjObjEntrySet = rootArrayObjObj.entrySet();
for (Map.Entry<String, JsonElement> rootArrayObjObjChild : rootArrayObjObjEntrySet) {
/*
* Getting each object, key or array as an element
*/
JsonElement rootArrayObjObjElement = rootArrayObjObj.get(rootArrayObjObjChild.getKey());
if(rootArrayObjObjElement.isJsonPrimitive()){
}else if(rootArrayObjObjElement.isJsonArray()){
JsonArray rootArrayObjArray = rootArrayObjObjElement.getAsJsonArray();
for (int j = 0; j < rootArrayObjArray.size(); j++) {
}
}
}
}else if(rootArrayObjElement.isJsonPrimitive()){
}
}
}
You can try something like below :
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class GSonWay {
public static void main(String[] args) throws Exception {
String jsonString = "{\n"
+ "\"items\": [{\n"
+ " \"id\": 633706061003,\n"
+ " \"fromNumber\": \"16572307534\",\n"
+ "\n"
+ " \"contact\": {\n"
+ " \"id\": 499354453003,\n"
+ " \"homePhone\": \"16572307534\"\n"
+ " },\n"
+ "\n"
+ " \"records\": [{\n"
+ " \"id\": 353389055003,\n"
+ " \"result\": \"LA\",\n"
+ " \"recordings\": [{\n"
+ " \"id\": 16427622003\n"
+ " }]\n"
+ " }]\n"
+ "}],\n"
+ "\"limit\": 100,\n"
+ "\"offset\": 0,\n"
+ "\"totalCount\": 5949\n"
+ "\n"
+ "}";
List keys1 = getKeysFromJson(jsonString);
System.out.println(keys1.size());
System.out.println(keys1);
}
static List getKeysFromJson(String jsoString) throws Exception {
Object things = new Gson().fromJson(jsoString, Object.class);
List keys = new ArrayList();
collectAllTheKeys(keys, things);
return keys;
}
static void collectAllTheKeys(List keys, Object o) {
Collection values = null;
if (o instanceof Map) {
Map map = (Map) o;
keys.addAll(map.keySet()); // collect keys at current level in hierarchy
values = map.values();
} else if (o instanceof Collection) {
values = (Collection) o;
} else{return;}
for (Object value : values) {
collectAllTheKeys(keys, value);
}
}
}
Output :
[items, limit, offset, totalCount, id, fromNumber, contact, records, id, homePhone, id, result, recordings, id]
Gson is one of the best ways to decode JSON file. Gson requires POJO classes, which can be manually generated but is tiresome. The best way to develop POJO classes is to visit jsonschema2pojo.org. They will generate the required POJO classes for you.
Let's say the class is JsonData.java
So in your code, you have to create a Gson object as well as an object of JsonData class.
String jsonFile = "____let this be your json data___"
Gson gson = new Gson();
JsonData jsonData = new JsonData();
jsonData = gson.fromJson(jsonFile, JsonData.class);
now jsonData will have all the data's retrieved from the json file. If you want to get fromNumber you can just call getFromNumber() which will be a method inside JsonData class. Similarly you can call other values too.
Try using third party services like jsonschema2pojo.org for improving your productivity.
Is it possible to use Jackson library to manually parse JSON?
I.e. I don't want to use ObjectMapper and convert JSON to some object, but rather I want select some individual properties from JSON, like in XPath:
For example this is my JSON:
{
"person": {
"name": "Eric",
"surname": "Ericsson",
"address" {
"city": "LA",
"street": "..."
}
}
}
And all what I want is just to get Name and the City, for this cases I don't want introduce 2 new Java classes (Person and Address) and use them with ObjectMapper, but I'm just want to read this values like in xPath:
Pseudocode:
String name = myJson.get("person").get("name")
String city = myJson.get("person").get("address").get("city")
You can use the Jackson tree model and JsonNode#at(...) method which takes the Json Pointer expression as a parameter.
Here is an example:
public class JacksonJsonPointer {
static final String JSON = "{"
+ " \"person\": {"
+ " \"name\": \"Eric\","
+ " \"surname\": \"Ericsson\","
+ " \"address\": {"
+ " \"city\": \"LA\","
+ " \"street\": \"...\""
+ " }"
+ " }"
+ "}";
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
final JsonNode json = mapper.readTree(JSON);
System.out.println(json.at("/person/name"));
System.out.println(json.at("/person/address/city"));
}
}
Output:
"Eric"
"LA"
Yes Using Json parser you can parse your Json, Below is a sample example you can find more in jackson documentation
JsonParser jsonParser = new JsonFactory().createJsonParser(jsonStr);
while(jsonParser.nextToken() != JsonToken.END_OBJECT){
String name = jsonParser.getCurrentName();
if("name".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
if("surname".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
if("city".equals(name)) {
jsonParser.nextToken();
System.out.println(jsonParser.getText());
}
}
Before this is marked as a duplicate please read the question (I did look at similar ones). Thank you.
For simplicity, assume I have JSON like this:
{
"clients" : [
{
"name" : "client 1",
"id" : 1
},
{
"name" : "client 2",
"id" : 2
}
],
"other" : {
"something" : ""
}
...
}
So I want to create a hash map of only the clients and their fields. The basic question is how would I go about doing this using Jackson methods for a single JSON array like clients? I've tried to look online but all of the examples that I have seen either don't use Jackson or only are for a single JSON object like so:
HashMap<String, String>[] values = new ObjectMapper().readValue(jsonString, new TypeReference<HashMap<String, String>[]>() {});
I've also seen Gson examples and I know I can do some string parsing magic:
jsonSting = jsonString.substring(jsonString.indexOf("["), (jsonString.indexOf("]")+1))
to get it in a format that I can use, but I want to try it with Jackson to avoid importing another library. Any ideas?
Rephrasing the question:
So if I only had a list of clients like so:
jsonString = [{"name" : "client 1","id" : 1},{"name" : "client 2","id" : 2}]
then I could just do:
HashMap[] values = new ObjectMapper().readValue(jsonString, new TypeReference[]>() {});
to get what I want. I am basically asking if there is a way using Jackson methods to get the jsonString above from the large JSON section on top. I know I can easily do it with this example with string parsing but there will be more complex situations in the future and string parsing is not really considered best practice
You can extract a part of the JSON tree using the Jackson tree model API and then convert it to an array of maps.
Here is an example:
public class JacksonReadPart {
public static final String JSON = "{\n" +
" \"clients\" : [\n" +
" {\n" +
" \"name\" : \"client 1\",\n" +
" \"id\" : 1\n" +
" },\n" +
" {\n" +
" \"name\" : \"client 2\",\n" +
" \"id\" : 2\n" +
" }\n" +
"],\n" +
" \"other\" : {\n" +
" \"something\" : \"\"\n" +
" }\n" +
"\n" +
"}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(JSON).path("clients");
// non type safe
Map<String, Object>[] clients = mapper.treeToValue(node, Map[].class);
System.out.println(Arrays.toString(clients));
// type safe
JsonParser parser = mapper.treeAsTokens(node);
clients = parser.readValueAs(new TypeReference<Map<String, Object>[]>() {});
System.out.println(Arrays.toString(clients));
}
}
Output:
[{name=client 1, id=1}, {name=client 2, id=2}]
[{name=client 1, id=1}, {name=client 2, id=2}]
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...
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());
}
}
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.
I also had to solve a similar problem in my project and found out that springframework.vault has a method flatten() to do the same. Below is a sample code.
//Json string to Map<String, Object>
String data = "Your json as string"
final ObjectMapper mapper = new ObjectMapper();
final MapType type = mapper.getTypeFactory().constructMapType(
Map.class, String.class, Object.class);
final Map<String, Object> map = mapper.readValue(data, type);
//Using springframework.vault flatten method
Map<String, String> keyMap = JsonMapFlattener.flattenToStringMap(map);
//Input
{"key": {"nested": 1}, "another.key": ["one", "two"] }
//Output
key.nested=1
another.key[0]=one
another.key[1]=two
Remember to add the dependency
<dependency>
<groupId>org.springframework.vault</groupId>
<artifactId>spring-vault-core</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
For more info, refer https://docs.spring.io/spring-vault/docs/current/api/org/springframework/vault/support/JsonMapFlattener.html
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);
}
}
}
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?