I have a JSON which looks like this (number of fields heavily reduced for the sake of example):
{
"content": {
"id": {"content": "1"},
"param1": {"content": "A"},
"param2": {"content": "55"}
}
}
Keep in mind, that I don't have control over it, I can't change it, that is what I get from API.
I've created a POJO class for this looking like that:
public class PojoClass {
private String id;
private String param1;
private String param2;
// getters and setters
}
Then I parse JSON with Jackson (I have to use it, please don't suggest GSON or else):
ObjectMapper om = new ObjectMapper();
JsonNode jsonNode = om.readTree(json).get("content");
PojoClass table = om.readValue(jsonNode.toString(), PojoClass.class);
And this doesn't work, because of id, param1 and param2 having JSON in them, not straight values. The code works fine with JSON like this:
{
"content": {
"id": "1",
"param1": "A",
"param2": "55"
}
}
But unfortunately the values I need are stored under "content" fields.
What is the cleanest way to resolve this?
I understand that I can hardcode this and extract all values into variables one by one in constructor or something, but there are a lot of them, not just 3 like in this example and obviously this is not the correct way to do it.
You can modify the JsonNode elements like "id": {"content": "1"} to {"id": "1"} inside your json string accessing them as ObjectNode elements with an iterator and after deserialize the new json obtained {"id":"1","param1":"A","param2":"55"} like below:
String content = "content";
ObjectMapper om = new ObjectMapper();
JsonNode root = om.readTree(json).get(content);
Iterator<String> it = root.fieldNames();
while (it.hasNext()) {
String fieldName = it.next();
((ObjectNode)root).set(fieldName, root.get(fieldName).get(content));
}
PojoClass table = om.readValue(root.toString(), PojoClass.class);
System.out.println(table); //it will print PojoClass{id=1, param1=A, param2=55}
I would like the guidence from you all, i'm confused in how to go on in a situation at Java + Spring Boot.
I receive from the database 2 columns of strings, the first column is a path separeted by a slash(/) like "crumbs[0]/link/path" and the second column have the value assigned to the first column, and what i'm trying to do is to create a nested JSON with this.
For example, i'm receiving from the database the following response in two columns like a said before:
COLUMN 1(PATH), COLUMN 2(VALUE)
"crumbs[0]/link/path", "/pokemon/type/pokemon?lang=en"
"crumbs[0]/link/wid", "tablePokemon",
"crumbs[0]/name", "Pokemon"
"data/records[1]/id", "Pikachu"
"data/records[1]/link/path": "/pokemon/type/eletric/pikachu",
"data/records[1]/link/wid": "tableEletric",
"data/records[1]/available": "true",
"data/records[2]/id", "Bulbasaur"
"data/records[2]/link/path": "/pokemon/type/grass/bulbasaur",
"data/records[2]/link/wid": "tableGrass",
"data/records[2]/available": "true",
With this response from database, i'm trying to get this result in Java:
"crumbs": [
{
"link": {
"path": "/pokemon/type/pokemon?lang=en",
"wid": "tablePokemon"
},
"name": "Pokemon"
}
],
"data": {
"records": [
{
"id": "Pikachu",
"link": {
"path": "/pokemon/type/eletric/pikachu",
"wid": "tableEletric"
},
"available": "true",
},
{
"id": "Bulbasaur",
"link": {
"path": "/pokemon/type/grass/bulbasaur",
"wid": "tableGrass"
},
"available": "true",
}
]
}
You guys would have any suggestions for me to achieve this objective?
Thank you all for your time, appreciate any help.
You can easily construct a JSON with com.fasterxml.jackson.core.JsonPointer!
Some details on parsing a JsonPath and constructing a Json from it is mentioned here How to add new node to Json using JsonPath?
You could made use of the code from the above reference to build your code.
Add the com.fasterxml.jackson dependencies to your pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1.2</version>
</dependency>
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) {
/** I'm creating a below map to hold the values you have mentioned in the above case.
While using JsonPointer I found two issues with the key mentioned here
1. Key doesnt start with a / . I'm appending a / with the key while inserting to map
2. The arrays in data eg:crumbs[0]/link/path should be represented like crumbs/0/link/path ( I haven't handled this in the code, but it doesn't make much difference in the output)
**/
Map<String, String> databaseKeyValues = new HashMap<String, String>();
databaseKeyValues.put("crumbs[0]/link/path", "/pokemon/type/pokemon?lang=en");
databaseKeyValues.put("crumbs[0]/link/wid", "tablePokemon");
databaseKeyValues.put("crumbs[0]/name", "Pokemon");
databaseKeyValues.put("data/records[1]/id", "Pikachu");
databaseKeyValues.put("data/records[1]/link/path", "/pokemon/type/eletric/pikachu");
databaseKeyValues.put("data/records[1]/link/wid", "tableEletric");
databaseKeyValues.put("data/records[1]/available", "true");
databaseKeyValues.put("data/records[2]/id", "Bulbasaur");
databaseKeyValues.put("data/records[2]/link/path", "/pokemon/type/grass/bulbasaur");
databaseKeyValues.put("data/records[2]/link/wid", "tableGrass");
databaseKeyValues.put("data/records[2]/available", "true");
ObjectNode rootNode = mapper.createObjectNode();
for(java.util.Map.Entry<String, String> e:databaseKeyValues.entrySet()) {
setJsonPointerValue(rootNode, JsonPointer.compile("/"+e.getKey()), //Adding slash to identify it as the root element, since our source data didn't have proper key!
new TextNode(e.getValue()));
}
try {
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode));
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
}
public static void setJsonPointerValue(ObjectNode node, JsonPointer pointer, JsonNode value) {
JsonPointer parentPointer = pointer.head();
JsonNode parentNode = node.at(parentPointer);
String fieldName = pointer.last().toString().substring(1);
if (parentNode.isMissingNode() || parentNode.isNull()) {
parentNode = mapper.createObjectNode();
setJsonPointerValue(node,parentPointer, parentNode); // recursively reconstruct hierarchy
}
if (parentNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) parentNode;
int index = Integer.valueOf(fieldName);
// expand array in case index is greater than array size (like JavaScript does)
for (int i = arrayNode.size(); i <= index; i++) {
arrayNode.addNull();
}
arrayNode.set(index, value);
} else if (parentNode.isObject()) {
((ObjectNode) parentNode).set(fieldName, value);
} else {
throw new IllegalArgumentException("`" + fieldName + "` can't be set for parent node `"
+ parentPointer + "` because parent is not a container but " + parentNode.getNodeType().name());
}
}
Output:
{
"data" : {
"records[1]" : {
"id" : "Pikachu",
"link" : {
"wid" : "tableEletric",
"path" : "/pokemon/type/eletric/pikachu"
},
"available" : "true"
},
"records[2]" : {
"available" : "true",
"link" : {
"wid" : "tableGrass",
"path" : "/pokemon/type/grass/bulbasaur"
},
"id" : "Bulbasaur"
}
},
"crumbs[0]" : {
"link" : {
"path" : "/pokemon/type/pokemon?lang=en",
"wid" : "tablePokemon"
},
"name" : "Pokemon"
}
}
The json arrays records[1], records[2], crumbs[0] would be sorted out once we handle the JsonPath from crumbs[0]/link/path to crumbs/0/link/path. Just some string operations would help (iterate through the values and replace '[0]' with '/0/', you could write a regex to pattern match and replace!).
You will need to parse the paths, then build some kind of tree object in memory, and finally convert the tree that you built into JSON.
Here are some tips:
Start by defining an empty root element. You can use a Map. Keys will be strings, values will be either strings, lists or maps.
For each path, split it by "/".
For each path element except the last, check if it is a list or a subtree. You can distinguish this by the presence of [n] at the end of the string.
Create all intermediate nodes for the path except for the last one. Starting from root (which is a Map), add either a List or a Map for each element if it doesn't exist yet under that name. If it already exists, check that it is what you need it to be. In case of List, append the element. In case of Map, create a sub-entry.
For the last path element, add it as a String.
Repeat this for all paths to fill your tree.
When you are finished, use a combination of recursion and StringBuiders to construct the output string. Alternatively, if you only used strings, maps and lists, you can also use a library such as Jackson to produce JSON.
Note that you don't have information about the length of the lists, so this conversion will not be reversible.
I have an issue with JSON data.
In my Json it is having data types with values. not sure how to parse.
ex:
{
"id": "123456",
"name": {
"firstName": {
"String": "Nathon"
},
,
"lastName": {
"String": "Jason"
}
}.
Please help on this
public String map(ObjectNode jsonNode) throws Exception {
return value.get("id");
}
I tried with the above sample code , but i am able to parse only "id"
If you are using Jackson2 then get then name as JsonNode
JsonNode nameNode = value.path("name");
And then again get the firstName and lastName as JsonNode
JsonNode firstName = nameNode.path("firstName");
JsonNode lastName = nameNode.path("lastName");
From JsonNode firstName and JsonNode lirstName get the string value
String name1 = firstName.path("string").asText();
String name2 = lastName.path("string").asText();
Well first of all you don't have to mention the data types in order to parse a JSON appropriately, just create a POJO class matching the structure of JSON then use GSON to parse JSON into a java class
When your json is deserialized into ObjectNode then it is actually represented internally as a map key/value in which value can itself be again a map as is in your case. Visually if you look at it, it would be something like this.
So you would need to follow this structure using get(fieldName) to get the value OR an ObjectNode if it is nested . Remember if the return value is ObjectNode then simply printing it would just return the json fragment it represents, so you would need to call again 'get(fieldName)' on that object.
I have below json input String containing node TestInfo
{
"TestInfo" : [ {
}
]
}
I want to add container node to the TestInfo like below
{
"TestInfoContainer":{
"TestInfo" : [ {
}
]
}
}
Kindly suggest me way to do this with good performance as this could be applicable to actual big hierarchical structure
Is it easy with any JSON parser
Is it easy with string manipulation
With JSON parser its easy
JSONObject node = new JSONObject(nodeJsonString);
JSONObject container = new JSONObject(containerJsonString);
container.put("TestInfoContainer", node.getJSONArray("TestInfo"));
(EDIT) Given nested JSON object
{"TestInfo":[{}],"A":{"B":{"C":{"TestInfo":[{}]}}},"D":{"TestInfo":[{}]},"E":{"F":{"G":{"H":{"TestInfo":[{}]}}}}}
You get value down hierarchy by calling methods in chain
container.getJSONObject("TestInfoContainer").put("TestInfo1",jObject.getJSONArray("TestInfo"));
container.getJSONObject("TestInfoContainer").put("TestInfo2",jObject.getJSONObject("A")
.getJSONObject("B")
.getJSONObject("C")
.getJSONArray("TestInfo"));
container.getJSONObject("TestInfoContainer").put("TestInfo3",jObject.getJSONObject("D")
.getJSONArray("TestInfo"));
container.getJSONObject("TestInfoContainer").put("TestInfo4",jObject.getJSONObject("E")
.getJSONObject("F")
.getJSONObject("G")
.getJSONObject("H")
.getJSONArray("TestInfo"));
I have a json schema file which follows the custom rules I've created. This schema file is a valid json file. It follows the below pattern.
{
"name": {
"fname" : {
"displayName":"FirstName",
"dataType":"String"
}
"lname" : {
"displayName":"LastName",
"dataType":"String"
}
},
"address": {
"displayName":"Address",
"dataType":"String"
}
}
so based on the schema I need to create the below json with respective values.
{
"name": {
"FirstName": "test",
"LastName" : "test1"
},
"Address" : "someAddress"
}
So when I get the schema, what is the best way to find the config information node? That is the leaf node which has the displayName and dataType parameters. Currently I'm traversing this tree using Jackson json and finding the nodes with displayName and dataType keys. Because I cannot precisely say at which level this leaf node could be present. Is there a better way to handle this situation than traversing the whole json tree looking for the elements?
I was not sure wnat exactly is required (do you want the fname object or the value of its properties), however, JsonPath seems like a good fit here. It is the equivalent of xpath for json - search hierarchy model based on various criteria
I made a small demo to get you started. you just need to twaek the query string to suit your requirements. You can use the Jayway JsonPath Evaluator as REPL
import java.nio.file.*;
import com.jayway.jsonpath.*;
public class JsonPathDemo
{
public static void main(String[] args)
{
// query: get all json objects that have displayName and dataType properties
String jsonPathQuery = "$..*[?(#.displayName && #.dataType)]";
try {
String content = new String(Files.readAllBytes(Paths.get("C://temp/xx.json")));
Object parsedContent = Configuration.defaultConfiguration().jsonProvider().parse(content);
Object configElements = JsonPath.read(parsedContent, jsonPathQuery);
System.out.println(configElements.getClass());
System.out.println(configElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
output is
class net.minidev.json.JSONArray
[{"displayName":"Address","dataType":"String"},{"displayName":"FirstName","dataType":"String"},{"displayName":"LastName","dataType":"String"}]
EDIT: answer for question in comment:
It is possible to check for path existence (and make all sorts of other assertions) using json-path-assert:
import static com.jayway.jsonpath.matchers.JsonPathMatchers.*;
// query for specific path
String jsonSpecificPathQuery = "$..name.fname.displayName";
String content = new String(Files.readAllBytes(Paths.get("C://temp/xx.json")));
Object parsedContent = Configuration.defaultConfiguration().jsonProvider().parse(content);
System.out.println("hasJsonPath? " + hasJsonPath(jsonSpecificPathQuery).matches(parsedContent));