'Create object hierarchy with string prefixes
I'm working on a project where a REST endpoint responds with a flat list of objects. These objects have a String name
, a String shorthand
and a String structure
which contains all the parent shorthands separated by a comma ,
. The structure
property includes information of the parent-child relationship between the objects. Example structure="abc,abc_DEF,abc_DEF_persons"
. So, abc
is a parent object, abc_DEF
its child object, abc_DEF_persons
the child of the child object.
Now my job is to restore the hierarchy in a Java-based service after receiving the list from the endpoint. They are received in no particular order.
response.json
[
{
"name": "Object1", // parent
"shorthand": "abc",
"structure": null
},
{
"name": "Object2", // child of abc
"shorthand": "abc_DEF",
"structure": "abc"
},
{
"name": "Object3", // child of abc_DEF
"shorthand": "abc_DEF_123",
"structure": "abc,abc_DEF"
},
{
"name": "Object4", // child of abc_DEF
"shorthand": "abc_DEF_456",
"structure": "abc,abc_DEF"
},
{
"name": "Object5", // parent
"shorthand": "xyz",
"structure": null
},
{
"name": "Object6", // child of xyz
"shorthand": "xyz_UVW",
"structure": "xyz"
},
{
"name": "Object7", // child of xyz
"shorthand": "xyz_RST",
"structure": "xyz"
}
]
Item.java
public class Item {
public String name;
public String shorthand;
public String structure;
public List<Item> items;
public Item() {
}
public Item(String name, String shorthand, String structure, List<Item> items) {
this.name = name;
this.shorthand = shorthand;
this.structure = structure;
this.items= items;
}
}
ItemService.java
private createHierarchicalItems(List<Item> flatItems) {
// Create generic root item
Item root = new Item();
// Sort items by length of structure (null first)
flatItems.sort((a, b) -> {
if(a.structure != null && b.structure != null) {
return a.structure.split(",").length - b.structure.split(",").length;
}
return (a.structure == null) ? -1 : 1;
});
// Iterate over the flatItems from the HTTP response
for(Item flatItem of flatItems) {
List<String> parentShorthands = new ArrayList<>();
// If the structure property has content, create a list of parent shorthands
if(flatItem.structre != null && flatItem.structure.length > 0) {
parentShorthands = flatItem.structure.split(",");
}
buildTree(root, parentShortHands); // TBD
}
}
My current approach is to create a tree structure by building it recursively, but I´m still struggling with the buildTree method.
expectedStructure.json
[
{
"name": "Object1",
"shorthand": "abc",
"structure": null,
"categories": [
{
"name": "Object2",
"shorthand": "abc_DEF",
"structure": "abc",
"categories": [
{
"name": "Object3",
"shorthand": "abc_DEF_123",
"structure": "abc,abc_DEF",
"categories": []
},
{
"name": "Object4",
"shorthand": "abc_DEF_456",
"structure": "abc,abc_DEF",
"categories": []
}
]
}
]
},
{
"name": "Object5",
"shorthand": "xyz",
"structure": null,
"categories": [
{
"name": "Object6",
"shorthand": "xyz_UVW",
"structure": "xyz",
"categories": []
},
{
"name": "Object7",
"shorthand": "xyz_RST",
"structure": "xyz",
"categories": []
}
]
}
]
Solution 1:[1]
Assuming that the last part of a structure is always the immediate parent, I was able to create the expected output with following code:
public class Test {
public static void main(String[] args) {
// mix up order of input
List<Item> flatItems = new ArrayList<>();
flatItems.add(new Item("Object2", "abc_DEF", "abc"));
flatItems.add(new Item("Object3", "abc_DEF_123", "abc,abc_DEF"));
flatItems.add(new Item("Object4", "abc_DEF_456", "abc,abc_DEF"));
flatItems.add(new Item("Object1", "abc", null));
flatItems.add(new Item("Object6", "xyz_UVW", "xyz"));
flatItems.add(new Item("Object5", "xyz", null));
flatItems.add(new Item("Object7", "xyz_RST", "xyz"));
// sort items. Can also be done in createHierarchicalItems method
flatItems
.sort(Comparator.comparing(Item::getStructureLength, Comparator.nullsFirst(Comparator.naturalOrder())));
List<Item> createHierarchicalItems = createHierarchicalItems(flatItems);
printItems(createHierarchicalItems);
}
private static void printItems(List<Item> createHierarchicalItems) {
//only used to structure printing
for (Item item : createHierarchicalItems) {
System.out.println(item);
if (item.categories.size() > 0) {
printItems(item.categories);
}
}
}
private static List<Item> createHierarchicalItems(List<Item> flatItems) {
List<Item> parents = new ArrayList<>();
for (Item flatItem : flatItems) {
// check if the item is a parent item
if (flatItem.structure == null)
parents.add(flatItem);
else {
// if not a parent, search for immediate parent
findImmediateParent(parents, flatItem);
}
}
return parents;
}
private static void findImmediateParent(List<Item> parents, Item flatItem) {
for (Item parent : parents)
// check if the parent item is immediate parent, based on the fact that last item of structure is immediate parents shorthand
if (parent.shorthand.equals(flatItem.structure.split(",")[flatItem.structure.split(",").length - 1])) {
parent.addChild(flatItem);
} else if (parent.categories.size() > 0) {
// search for possible parents in children of current parent
findImmediateParent(parent.categories, flatItem);
}
}
}
class Item {
public String name;
public String shorthand;
public String structure;
public List<Item> categories;
public Item() {
}
// utility method to add a child item
public void addChild(Item flatItem) {
categories.add(flatItem);
}
public Item(String name, String shorthand, String structure) {
this.name = name;
this.shorthand = shorthand;
this.structure = structure;
categories = new ArrayList<>();
}
// used for sorting
Integer getStructureLength() {
return structure != null ? structure.split(",").length : null;
}
@Override
public String toString() {
return "Item [name=" + name + ", shorthand=" + shorthand + ", structure=" + structure + "]";
}
}
Here is the output of the print:
Item [name=Object1, shorthand=abc, structure=null]
Item [name=Object2, shorthand=abc_DEF, structure=abc]
Item [name=Object3, shorthand=abc_DEF_123, structure=abc,abc_DEF]
Item [name=Object4, shorthand=abc_DEF_456, structure=abc,abc_DEF]
Item [name=Object5, shorthand=xyz, structure=null]
Item [name=Object6, shorthand=xyz_UVW, structure=xyz]
Item [name=Object7, shorthand=xyz_RST, structure=xyz]
and a look at the debugging view, making it clear which item has which children.
Solution 2:[2]
sort raw items by depth, ascending
loop over items and put them in map, key is the item path, in this case structure + shorthand
get the parent by key = item structure
add item to child list
public class RawItem {
public static class RawItemComparator implements Comparator<RawItem> { @Override public int compare(RawItem o1, RawItem o2) { int depth = null == o1.structure ? 0 : o1.structure.split(",").length; int otherDepth = null == o2.structure ? 0 : o2.structure.split(",").length; if(depth == otherDepth) { return 0; } else if(depth > otherDepth) { return 1; } else { return -1; } } @Override public boolean equals(Object obj) { return false; } } private final String name; private final String shorthand; private final String structure; @JsonCreator public RawItem(@JsonProperty("name") String name, @JsonProperty("shorthand") String shorthand, @JsonProperty("structure") String structure) { this.name = name; this.shorthand = shorthand; this.structure = structure; } public String getName() { return name; } public String getShorthand() { return shorthand; } public String getStructure() { return structure; } @Override public String toString() { return "RawItem{" + "name='" + name + '\'' + ", shorthand='" + shorthand + '\'' + ", structure='" + structure + '\'' + '}'; }
}
main class
public class Starter {
public static void main(String[] args) throws IOException {
Map<String, Item> itemMap = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
List<RawItem> items = Arrays.asList(mapper.readValue(mapper.getClass().getResourceAsStream("/data.json"), RawItem[].class));
items.sort(new RawItem.RawItemComparator());
items.forEach(item -> {
Item parent = null == item.getStructure() ? null : itemMap.get(item.getStructure());
Item current = new Item(item.getName(), item.getShorthand(), item.getStructure(), new ArrayList<>());
if(parent != null) {
parent.items.add(current);
}
itemMap.put(current.structure == null ? current.shorthand : current.structure + "," + current.shorthand, current);
});
}
}
Solution 3:[3]
Read the input into a List<Item>
where the name
, shorthand
, and structure
are populated and the items
List is empty (but not null).
List<Item> itemList = readItemsFromFile(..);
Map these items using the shorthand
as the key.
Map<String, Item> itemMap = mapItems(items);
At this point, all Items are in itemList
and all Items are in itemMap
. We want itemList
to contain only the root objects (i.e. those with no parent) but to have all of the child objects appear in the parent.items List of their parent. To do this, we'll iterate over the itemList
using an Iterator
. For a given Item
, if it has no structure then we skip it because it is a root Item. Otherwise, we identify the parent from the structure, fetch the parent from the itemMap
, and add this Item to its parent.items
List. Then invoke the iterator.remove()
to pull this elements from itemList
.
for (Iterator<Item> iterator = items.iterator() ; iterator.hasNext() ;) {
Item item = iterator.next();
if (null == item.getStructure()) {
continue;
}
iterator.remove();
String parentKey = identifyParent(item.getStructure());
Item parent = itemMap.get(parentKey);
Objects.requireNotNull(parent, "parentKey does not map to an Item "+parentKey);
parent.getItems().add(item);
}
When this iteration is complete, the itemList
elements will be the root Item
s, each populated with its children and the those children populated with their children, etc.
Solution 4:[4]
You will need to set the parent equal to 0 or -1 whatever you want in the database. Next, you will use a method to find all the parents (parent id equal to 0 or -1) and you'll create a list of arrays for each specific parent id. After creating the arraylist with the parents, you'll go and set the corresponding child in the specific arraylist. You can an int variable for setting the depth and etc.. you need two methods , one for finding the parents and one for creating the lists and recursive the method for searching for the childs and then setting them to the list , the hard part is to understand the second level and bellow of your literation. I hope it helps.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | XtremeBaumer |
Solution 2 | Marc Stroebel |
Solution 3 | vsfDawg |
Solution 4 | Konstantinos K. |