I currently have a class with several attributes describing a certain type of object in my system. This class is known as EnrollmentInfo.
I also have a hashmap that is structured as follows;
HashMap<EnrolmentInfo, List<EnrolmentInfo>> devices = new HashMap<>();
As it can be seen, the value properties in this hashmap contain an ArrayList of the EnrollmentInfo class type.
To provide some context, this hashmap is used to hold the parent nodes and associated child nodes of a tree structure as key, value pairs.
I generated this hashmap by traversing and extracting details from a child/parent table such as the following:
Child : Parent
1 : 0
2 : 0
3 : 2
4 : 0
5 : 4
6 : 4
7 : 1
8 : 6
The code for extracting the parents and children and putting them into the HashMap is as follows:
// Extracts the parents and assigns them to the key values
for (EnrolmentInfo enrolmentInfo : enrolmentInfos) {
Integer nodeParentId = enrolmentInfo.getParentId();
EnrolmentInfo parentEnrolmentInfo = dms.getDevice(nodeParentId).getEnrolmentInfo();
devices.put(parentEnrolmentInfo, new ArrayList<EnrolmentInfo>());
}
// Extracts the children and assigns them to the children arraylist of each associated parent.
for (EnrolmentInfo enrolmentInfo : enrolmentInfos) {
int nodeId = enrolmentInfo.getId();
Integer parentId = enrolmentInfo.getParentId();
EnrolmentInfo nodeEnrolmentInfo = dms.getDevice(nodeId).getEnrolmentInfo();
for (Map.Entry<EnrolmentInfo, List<EnrolmentInfo>> parentDevice : devices.entrySet()) {
if (parentDevice.getKey().getId() == parentId) {
parentDevice.getValue().add(nodeEnrolmentInfo);
break;
}
}
}
My issue now is to compose this hashmap into an actual tree structure such that it can be compiled into a human-readable form via a JSON library.
More specifically how can a nested tree structure be generated based on the HashMap mentioned above?
EDIT:
Shown below is an example structure of the kind of JSON format I'm expecting at the end.
{
"id" : 0,
"children" : [
{
"id" : 1,
"children" : [
{
"id" : 7,
"children" : []
}
]
},
{
"id" : 2,
"children" : [
{
"id" : 3,
"children" : []
}
]
},
{
"id" : 4,
"children" : [
{
"id" : 5,
"children" : []
},
{
"id" : 6,
"children" : [
{
"id" : 8,
"children" : []
}
]
}
]
}
]
}
EDIT:
So far I've created a bean class that is as follows:
public class DeviceHierarchyNode implements Serializable {
#ApiModelProperty(name = "id", value = "ID of the node generated. Same as Device ID",
required = true)
private int id;
#ApiModelProperty(name = "label", value = "Device name as suggested by the user.",
required = true)
private String label;
#ApiModelProperty(name = "children", value = "List of child devices associated with device if any",
required = true)
private List<DeviceHierarchyNode> children;
My plan is to use this to create the final nested structure.
Warning: hacky.
Could you create a node type that wraps your underlying:
public class EnrolmentInfoNode {
private EnrolmentInfo info;
private List<EnrolmentInfoNode> children;
public EnrolmentInfoNode(EnrolmentInfo contents) {
this.info = contents;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + info.getId();
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
EnrolmentInfoNode other = (EnrolmentInfoNode) obj;
if (info.getId() != other.info.getId())
return false;
return true;
}
public void addChild(EnrolmentInfoNode child) {
if (children == null) {
children = new ArrayList<>();
}
children.add(child);
}
}
And then re-map thus:
Map<EnrolmentInfo, EnrolmentInfoNode> nodeMap = new HashMap<>();
for (Entry<EnrolmentInfo, List<EnrolmentInfo>> entry : map.entrySet()) {
for (EnrolmentInfo child : entry.getValue()) {
EnrolmentInfoNode childNode = nodeMap.computeIfAbsent(child, EnrolmentInfoNode::new);
nodeMap.computeIfAbsent(entry.getKey(), EnrolmentInfoNode::new)
.addChild(childNode);
}
}
Assuming you know node 0 is the parent:
String json = new GsonBuilder().setPrettyPrinting()
.create()
.toJson(nodeMap.get(enrolmentInfo0));
System.out.println(json);
If you don't, you can add a "parentNode" field to the EnrolmentInfoNode and then scan the node map to find the first one that has a null parent (therefore, root), and you're off to the races.
Related
The question is quite simple:
From this:
{
"categoryId":"some_id",
"properties": {
"id": "braja_de_nana",
"displayName": "test",
"longDescription": "<p>TESTE</p>",
"active": true,
"attributes": [
{
"name": "made",
"value": "THIS_BECOMES_A_NODE_VALUE",
"property": "THIS_BECOMES_A_NODE_NAME"
},
{
"name": "made",
"value": "THIS_BECOMES_A_NODE_VALUE_2",
"property": "THIS_BECOMES_A_NODE_NAME_2"
}
]
}
}
UPDATE
This should be the result:
It means that every array element of 'attributes' should become a new root node.
set 'property' from 'attributes' as the object node name.
set 'value' from 'attributes' as the object node value.
{
"categoryId":"some_id",
"THIS_BECOMES_A_NODE_VALUE":"THIS_BECOMES_A_NODE_NAME",
"THIS_BECOMES_A_NODE_NAME_2":"THIS_BECOMES_A_NODE_VALUE_2"
"properties": {
"id": "braja_de_nana",
"displayName": "test",
"longDescription": "<p>TESTE</p>",
"active": true
}
}
This is a challenge for me.
I can set new nodes into the root node.
Already got a map from 'attributes' and then tried to iterate them with forEach in order to put the result into one single node, but instead as shown I have to take the 'property' set it to the object name´s key, then get the value and set to its value.
UPDATE 2
#Override
public String toOccProductDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
OccProductDTO occProductDTO = OccProductDTO.builder()
.categoryId(processProductDTO.getCategoryId())
.productType(processProductDTO.getCategoryId())
.properties(toOccProductPropertiesDTO(processProductDTO))
.build();
toOccProductPropertiesDTO(processProductDTO);
String tree = mapper.writeValueAsString(occProductDTO);
JsonNode root = mapper.readTree(tree);
JsonNode attributesNodeArray = ((ObjectNode) root.get("properties"))
.remove("p_specs");
Iterator<JsonNode> arrayNodes = attributesNodeArray.iterator();
while (arrayNodes.hasNext()) {
JsonNode node = arrayNodes.next();
root = ((ObjectNode)root).set(node.get("value").asText(), node.get("property"));
}
System.out.println(root.toPrettyString());
return null;
}
I got an: arrayNodes: Collection$EmptyIterator at that line.
Am I doing something wrong?
If you are trying to the attributes to the root node, you can remove that node and add its fields to the root.
The "attributes" node is an array with length 1, so you have to get the first element of the array to get the attribute fields.
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode attributesNodeArray = ((ObjectNode) root.get("properties"))
.remove("attributes");
JsonNode attributesNode = attributesNodeArray.get(0);
Iterator<String> fieldNames = attributesNode.fieldNames();
while (fieldNames.hasNext()) {
String name = fieldNames.next();
root = ((ObjectNode)root).set(name, attributesNode.get(name));
}
System.out.println(root.toPrettyString());
Output:
{
"categoryId" : "some_id",
"properties" : {
"id" : "braja_de_nana",
"displayName" : "test",
"longDescription" : "<p>TESTE</p>",
"active" : true
},
"name" : "made",
"value" : "some value",
"property" : "some_value"
}
UPDATE
For the updated question, you can do the following:
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode attributesNodeArray = ((ObjectNode) root.get("properties"))
.remove("attributes");
Iterator<JsonNode> arrayNodes = attributesNodeArray.iterator();
while (arrayNodes.hasNext()) {
JsonNode node = arrayNodes.next();
root = ((ObjectNode)root).set(node.get("value").asText(), node.get("property"));
}
System.out.println(root.toPrettyString());
Output:
{
"categoryId" : "some_id",
"properties" : {
"id" : "braja_de_nana",
"displayName" : "test",
"longDescription" : "<p>TESTE</p>",
"active" : true
},
"THIS_BECOMES_A_NODE_VALUE" : "THIS_BECOMES_A_NODE_NAME",
"THIS_BECOMES_A_NODE_VALUE_2" : "THIS_BECOMES_A_NODE_NAME_2"
}
Maybe it was more complex than expected.
It turns out that I solved the problem with Oboe´s help. Although he missed some points through his implementation plus some changes I could achieve the goal.
//Converts the parsed objects into Json String
String tree = mapper.writeValueAsString(occProductDTO);
//Reads the json string to JsonNode in order to manipulate it
JsonNode root = mapper.readTree(tree);
//Sets the chosen node where the new nodes should be created
JsonNode properties = root.path("properties");
//maps the two attribs needed
Map<String, String> attribs = processProductDTO.getProductDTO().getAttributes().stream()
.collect(Collectors.toMap(AttributeDTO::getProperty, AttributeDTO::getValue));
//Converts each attrib into a String list
List<String> props = attribs.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry<String, String>::getValue).reversed())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
List<String> names = attribs.entrySet()
.stream() .sorted(Comparator.comparing(Map.Entry<String,String>::getValue).reversed())
.map(Map.Entry::getValue)
.collect(Collectors.toList());
//iterates over the two lists adding the attribs to their corresponding position
Iterator<String> arrayNodes = props.listIterator();
Iterator<String> arrayNodes2 = names.listIterator();
while (arrayNodes.hasNext()) {
String node = arrayNodes.next();
String node2 = arrayNodes2.next();
properties = ((ObjectNode)properties).put(node, node2);
}
return mapper.writeValueAsString(root);
}
In the end, instead of passing a java object via #Post, I´m passing a json String by using "consumes = application/json, produces = "application.json"
That´s it!
Maybe it could be achieved and better implemented with java 8 stream, but for now it works.
Suggestion to improve the code are welcome!
I want to create a model from JSON where key is a value. This is the exact issue described, but in iOS. I want a similar solution in Android. Basically I want to have a Decodable equivalent in Android.
I am using GSON to parse JSON to model. As of now, I have compared the JSON key (mentioned in the link) against static values.
JSON :
{
"rows" :
[
{
"_id": "5cdc0ede5c3dcb04bdb3a972",
"emp_code": 187,
"log_id": 361711,
"punch_time": "2019-05-07T04:00:33.000Z",
"pin_type": 1,
"status": 4,
"__v": 0
},
{
"_id": "5cdc40de5c3dcb04bdb3a972",
"emp_code": 111,
"log_id": 361701,
"punch_time": "2019-05-07T04:00:35.000Z",
"pin_type": 101,
"status": 4,
"__v": 0
}
],
"pin_type_text": {
"1": "In Fingerprint",
"4": "In Card",
"101": "Out Fingerprint",
"104": "Out Card"
}
}
The value of pin_type in each row refers to the record in pin_type_text mapped with it's key.
I am using GSON for creating models, and here is the model class :
class MyModel {
var length : Long = 0
var rows = ArrayList<Rows>()
var pin_type_text : String = ""
}
class PinTypeText {
var 1 : String = ""
var 4 : String = ""
var 101 : String = ""
var 104 : String = ""
}
Basically, the keys defined in class PinTypeText are the values of the key 'pin_type' obtained in Rows model as seen in the JSON shared. So in this case, the keys in 'PinTypeText' are dependent on values defined in 'rows'. Hence, i want 'PinTypeText' model to be created with respect to 'pin_type' values from 'Rows' model.
Issue : Suppose in future, the 'pin_type' values - 1, 4, 101, 104 change in the backend, how can I handle such a case without changing my model. As per this model structure, I need to change my model class every time the backend model changes
you can store the item PinTypeText as a JsonElement and not as a custom class,
so your response model will be something like this
public class Response{
#SerializedName("rows")
#Expose
private List<Row> rows = null;
#SerializedName("pin_type_text")
#Expose
private JsonElement pinTypeText;
public List<Row> getRows() {
return rows;
}
public void setRows(List<Row> rows) {
this.rows = rows;
}
public JsonElement getPinTypeText() {
return pinTypeText;
}
public void setPinTypeText(JsonElement pinTypeText) {
this.pinTypeText = pinTypeText;
}
}
and when you want to check the type you can convert it to JsonObject and get the value for the key,
example
pinTypeText= response.getPinTypeText().asJsonObject().get([your pin_type here]).toString()
I'm trying to create a JSON document by using Jackson. The hierarchy goes as follows:
Event:
class Event {
private String name = "";
private Set<Integer> admin = new HashSet<>();
private List<House> houseList = new ArrayList<>();
}
House:
class House {
private List<OG> OGList = new ArrayList<>();
private int score = 0;
private String name = "";
}
Group:
class OG {
private int score = 0;
private int id = 0;
}
Every event might comprises of a set number of houses, which in turn comprises of a set number of groups. Each house and group has a score modifier as well.
Currently, this is how I print the JSON document using the pretty print method:
ObjectMapper mapper = new ObjectMapper();
File f = new File("./db/" + dir);
if (f.exists() && !f.isDirectory()) {
return "This event name is taken. Please try again.";
}
try {
mapper.writeValue(f, event);
// Convert object to JSON string and pretty print
String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(event);
System.out.println(jsonInString);
}
}
The resulting output is pretty ugly:
{
"name" : "test",
"admin" : [ 423766405 ],
"houseList" : [ {
"score" : 0,
"name" : "first",
"oglist" : [ {
"score" : 0,
"id" : 0
}, {
"score" : 0,
"id" : 1
}, {
"score" : 0,
"id" : 2
} ]
..
}
Is there a better way to format the output, for example:
name:
test
admin:
a
b
c
houses:
name:
first
group:
1
..
It appears like you want to output YAML, not JSON.
This answer shows how simple it is to write YAML output to a file using Jackson.
This answer shows how to read a YAML file, modify it's contents, and save it back out again.
I have table in mysql like this
Parent | Child
LevelOne LevelTwo
LevelOne LevelThree
LevelTwo LevelFour
LevelTwo LevelFive
LevelFour LevelSix
I have stored them in an ArrayList like this
LevelOne | LevelTwo
LevelOne |LevelThree
LevelTwo | LevelFour
LevelTwo |LevelFive
LevelFour |LevelSix
And I m trying to convert this into JSON. This is what I have tried so far -
for(String v : values){
String p = v.substring(0,v.indexOf("|"));//Parent
String c = v.substring(v.indexOf("|")+1);//Child
ObjectNode objectNode1 = mapper.createObjectNode();
objectNode1.put("Parent", p);
objectNode1.put("Children",c);
arrayNode.add(objectNode1);
}
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(arrayNode));
However this is wrong because it prints like this
[ {
"Folder" : "LevelOne",
"Folder" : "LevelTwo"
}, {
"Folder" : "LevelOne",
"Folder" : "LevelThree"
}, {
"Folder" : "LevelTwo",
"Folder" : "LevelFour"
}, {
"Folder" : "Horror",
"Folder" : "Werewolf"
}, and so on.
Instead of
{
"folder": "LevelOne",
"subfolders": [
{
"folder": "LevelTwo",
"subfolders": [
{
"folder": "LevelFour",
"subfolders": [
{
"folder": "LevelSix"
}
]
},
{
"folder": "LevelFive"
}
]
},
{
"folder": "LevelThree"
}
]
}
Please can you advise on how to format it like this?
So here is the solution to the problem you are trying to solve.
It's although not optimised and can be refactored.
Also, it doesn't handle the use-case where there exists two parents for same node.
public static void main(String[] args) throws JsonProcessingException {
ArrayList<String> values = new ArrayList<>();
values.add("LevelOne | LevelTwo");
values.add("LevelOne |LevelThree");
values.add("LevelTwo | LevelFour");
values.add("LevelTwo |LevelFive");
values.add("LevelFour |LevelSix");
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance);
for(String value : values){
String parent = value.substring(0,value.indexOf("|")).trim();
if(!arrayNode.findValuesAsText("folder").contains(parent)) {
buildNode(values, mapper, arrayNode, parent);
}
}
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(arrayNode));
}
//THIS WILL START BUILDING THE NODE
private static void buildNode(ArrayList<String> values, ObjectMapper mapper, ArrayNode arrayNode, String parent) {
ObjectNode rootNode = mapper.createObjectNode();
rootNode.put("folder", parent);
ArrayNode arrayNodeSubFolder = getSubFolders(values, mapper, parent);
if(arrayNodeSubFolder.size() != 0)
rootNode.put("subfolders", arrayNodeSubFolder);
arrayNode.add(rootNode);
}
//THIS WILL SCAN AND TRIGGER ADDING SUB NODE
private static ArrayNode getSubFolders(ArrayList<String> values, ObjectMapper mapper, String parent) {
ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance);
for (String val : values) {
String currentParent = val.substring(0,val.indexOf("|")).trim();//Parent
if(currentParent.equals(parent)) {
String child = val.substring(val.indexOf("|") + 1).trim();//Child
buildNode(values, mapper, arrayNode, child);
}
}
return arrayNode;
}
Also, i would recommend use of better data structure to represent the input data. However, this code is specifically as per your use-case
In the first place, I'd suggest creating the tree structure already in the mysql, i.e. save the parent reference instead of child in a separate column. This eliminates redundancy:
FolderName | Parent
-------------------------
"LevelOne" | null
"LevelTwo" | "LevelOne"
"LevelThree"| "LevelOne"
"LevelFour" | "LevelTwo"
"LevelFive" | "LevelTwo"
"LevelSix" | "LevelFour"
After you load the entities into array list (6 elements), you can sort them according to the "parent" field (the order would be actually the same as in the table above. Lastly, when you iterate over the collection, you iterate the tree from the top to bottom, so in each iteration, create a node and add it to corresponding parent object. I would strongly suggest using Objects instead of strings as a representation of the tuple.
I am getting a sql resultset in the form of flat data Which is in the following structure
L0|L1|L2|L3|value
n1|o1|p1|c1|3
n1|o1|p1|c2|2
n1|o1|p2|c1|1
n1|o2|p1|c1|0
n2|o2|p1|c1|5
Here L0,L1,L2,Value.. are column names and we can have more L's as well(it is dynamic)
I want it to convert into the following form
[{name:"n1",children:
[{name:o1,children:
[{name:"p1",children:
[{name:"c1",value:3},
{name:"c2",value:2}]
},{name:"p2",children:
[{name:"c1",value:"1"}]
}],]}.....
I want the result preferably in JSONArray or List structure.
Does anyone have algo or Code to do that in Java?
Thanks
Recursion is your friend.
The code below builds up the hierarchy from the input data into an intermediate model (A tree of "Nodes").
This is then turned into JSON using a recursive method...
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Stack {
public static void main(String[] args) {
// Can have as many columns as you want, always assume the last column is the value.
Object[][] dataFromResultSet = {
{ "n1", "o1", "p1", "c1", 3 },
{ "n1", "o1", "p1", "c2", 2 },
{ "n1", "o1", "p2", "c1", 1 },
{ "n1", "o2", "p1", "c1", 0 },
{ "n2", "o2", "p1", "c1", 5 }
};
Node root = new Node();
// Add all records to the hierachy
for (Object[] row : dataFromResultSet) {
addToHierachy(root, row);
}
// Convert hierachy to JSON
try {
JSONArray json = convertToJSON(root);
System.out.println(json);
} catch (JSONException e) {
System.out.println("Something went wrong converting hierachy to JSON");
e.printStackTrace();
}
}
private static void addToHierachy(Node root, Object[] row) {
Node current = root;
// Go through each column in the row
for(Object col : row) {
// If this column is a string, then it is a Branch node, not a value one
// (It might be better to iterate through the array using a counter instead
// and change this condition to say "if it isn't the last column"...)
if(col instanceof String) {
// Get (or create) the child node for this column
current = current.getOrCreateChild((String) col);
} else {
// Otherwise, set the value
current.setValue((Integer) col);
}
}
}
private static JSONArray convertToJSON(Node root) throws JSONException {
// Use recursion to build the result JSON
JSONArray array = new JSONArray();
// Starting at this root, go through all of the child entries
for(Map.Entry<String, Node> child : root.getChildren().entrySet()) {
Node childNode = child.getValue();
// New object for this entry...
JSONObject object = new JSONObject();
// Set the name
object.put("name", child.getKey());
// Set the value if it is present on this node
if(childNode.getValue() != null) {
object.put("value", childNode.getValue());
}
// Generate the child hierarchy if it has children
if(!childNode.getChildren().isEmpty()) {
JSONArray childHierachy = convertToJSON(childNode);
object.put("children", childHierachy);
}
array.put(object);
}
return array;
}
// Class used to build the hierarchy
static class Node {
// The map of children, LABEL -> NODE
private Map<String, Node> children = new HashMap<>();
// The value (kept as null if this does not have a value set)
private Integer value;
public Node getOrCreateChild(String key) {
Node node = children.get(key);
if(node == null) {
node = new Node();
children.put(key, node);
}
return node;
}
public Map<String, Node> getChildren() {
return children;
}
public Integer getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
}
Output:
[ {
"name" : "n1",
"children" : [ {
"name" : "o2",
"children" : [ {
"name" : "p1",
"children" : [ {
"name" : "c1",
"value" : 0
} ]
} ]
}, {
"name" : "o1",
"children" : [ {
"name" : "p2",
"children" : [ {
"name" : "c1",
"value" : 1
} ]
}, {
"name" : "p1",
"children" : [ {
"name" : "c1",
"value" : 3
}, {
"name" : "c2",
"value" : 2
} ]
} ]
} ]
}, {
"name" : "n2",
"children" : [ {
"name" : "o2",
"children" : [ {
"name" : "p1",
"children" : [ {
"name" : "c1",
"value" : 5
} ]
} ]
} ]
} ]