0

So I have a Map<String, ArrayList> parentToChild and want to create basically a "Family Tree" or nested hierarchy. Below is an example of the map but there could be more children at each level e.g. (Claire could have Matt and Bruce as children):

David -> [Claire]
Claire -> [Matt]
Matt -> [Sean, Terry]

I know the root of the tree should be David for the above example and it will only have one root.

Example output

{
 "David": {
   "Claire": {
      "Matt": {
        "Sean": {},
        "Terry": {}
      }
    }
  }
}

I've tried few things but genuinely stumped.

EDIT: Code tried so far

public Set<Tree> transform(Map<String, ArrayList<String>> input) {
        Set<String> roots = new HashSet<String>(input.keySet());

        
        Map<String, Tree> map = new HashMap<String, Tree>();

        for (Map.Entry<String, ArrayList<String>> entry : input.entrySet()) {
            String key = entry.getKey();
            List<String> childKeys = entry.getValue();
            Tree tree = map.get(key);
            if (tree == null) {
                tree = new Tree(key);
                map.put(key, tree);
            }
            for (String childKey : childKeys) {
                roots.remove(childKey);
                Tree child = map.get(childKey);
                if (child == null) {
                    child = new Tree(childKey);
                    map.put(childKey, child);
                }
                tree.addChild(child);
            }
        }
        Set<Tree> res = new HashSet<Tree>(roots.size());
        for (String key : roots) {
            res.add(map.get(key));
        }
        return res;
    }

Tree class:

public class Tree {
    private String key;
    private Tree child;

    public Tree(String key){
        this.key = key;
    }

    public void addChild(Tree child){
        this.child = child;
    }
}

The issue is when I use this code the output (What is in the set after debugging/printing) I get is

David:
  Claire:
    Matt:
     Terry:
7
  • 4
    "I've tried few things" Could you show what you tried? Are you trying to produce the JSON by hand, or do you have a specific library in mind that you need help with? Commented Jul 28, 2020 at 9:33
  • With producing the JSON was trying to do it in a general purpose way, as in take the root and make an json object for each child below it. So would loop over each one. I'll try and reproduce something have done (deleted and ctrl-z'd stuff so much in frustration) With regards libraries or making it from a string have no preference really. Commented Jul 28, 2020 at 9:50
  • What if the first entry on the map was David -> [Claire, Matt]? Do you encode Matt twice? Commented Jul 28, 2020 at 9:50
  • Sorry I forgot that detail will add it now. Only one child can have one parent. So David could not be the parent of Matt as well as Claire been the parent of Matt. Commented Jul 28, 2020 at 10:00
  • 1
    I can see two things here: 1 transform the initial structure to tree 2 serialize. the 2nd should be trivial if the structure is right. Commented Jul 28, 2020 at 10:05

2 Answers 2

2

You could use a Map<String,Object>:

private static final Gson GSON = new GsonBuilder()
        .setPrettyPrinting()
        .create();

public static void main(String[] args) {
    Map<String, List<String>> input = new HashMap<>();
    input.put("David", Arrays.asList("Claire"));
    input.put("Claire", Arrays.asList("Matt"));
    input.put("Matt", Arrays.asList("Sean", "Terry"));
    Map<String,Object> result = new HashMap<>();
    convert(input, "David", result);
    GSON.toJson(result, System.out);
}

private static void convert(Map<String, List<String>> input, String root,
        Map<String,Object> result) {
    if (!result.containsKey(root)) {
        Map<String,Object> rootObj = new HashMap<>();
        result.put(root, rootObj);
        List<String> children = input.get(root);
        if (children != null) {
            for (String child: children) {
                convert(input, child, rootObj);
            }
        }
    }
}

Output:

{
  "David": {
    "Claire": {
      "Matt": {
        "Terry": {},
        "Sean": {}
      }
    }
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Accepting as it is more of a Java-based solution. However, the Saxon solution will also work
0

In the Java world you have access to Saxon 9.8 or later HE where XPath 3.1 or XQuery 3.1 or XSLT 3.0 all have support for representing your initial map as an XdmMap and processing them, for instance with XQuery:

declare namespace map = "http://www.w3.org/2005/xpath-functions/map";

declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";

declare option output:method 'json';
declare option output:indent 'yes';

declare variable $map as map(xs:string, array(xs:string)) external := map {
    'David' : [ 'Claire' ],
    'Claire' : [ 'Matt' ],
    'Matt' : [ 'Sean', 'Terry' ]
};

declare variable $root as xs:string external := 'David';

declare function local:create-tree($map as map(xs:string, array(xs:string)), $children as xs:string*) as map(*) {
    map:merge($children ! map { . : local:create-tree($map, $map(.)) })
};

local:create-tree($map, $root)

https://xqueryfiddle.liberty-development.net/3Nzd8bV

A simple Java example to run this with Saxon 10 HE (its API documentation is at http://saxonica.com/html/documentation/using-xquery/api-query/s9api-query.html), passing a Java Map to the XQuery (inserted inline as a string but could of course be loaded from a file instead) is:

import java.util.HashMap;
import java.util.Map;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XQueryCompiler;
import net.sf.saxon.s9api.XQueryEvaluator;
import net.sf.saxon.s9api.XQueryExecutable;
import net.sf.saxon.s9api.XdmMap;

public class SaxonJavaMapToNestedJSONObject {


    public static void main(String[] args) throws SaxonApiException {
        
        Map<String, String[]> map = new HashMap<>();
        map.put("David", new String[] { "Claire" });
        map.put("Claire", new String[] { "Matt" });
        map.put("Matt", new String[] { "Sean", "Terry" });
        
        Processor processor = new Processor(true);
        
        XQueryCompiler compiler = processor.newXQueryCompiler();
        
        XQueryExecutable executable = compiler.compile("declare namespace map = \"http://www.w3.org/2005/xpath-functions/map\";\n" +
"\n" +
"declare namespace output = \"http://www.w3.org/2010/xslt-xquery-serialization\";\n" +
"\n" +
"declare option output:method 'json';\n" +
"declare option output:indent 'yes';\n" +
"\n" +
"declare variable $map as map(xs:string, array(xs:string)) external;\n" +
"\n" +
"declare variable $root as xs:string external := 'David';\n" +
"\n" +
"declare function local:create-tree($map as map(xs:string, array(xs:string)), $children as xs:string*) as map(*) {\n" +
"    map:merge($children ! map { . : local:create-tree($map, $map(.)) })\n" +
"};\n" +
"\n" +
"local:create-tree($map, $root)");
        
         XQueryEvaluator evaluator = executable.load();
         
         evaluator.setExternalVariable(new QName("map"), XdmMap.makeMap(map));
         
         evaluator.run(processor.newSerializer(System.out));
         
    }
    
}

Of course you could set the root variable as well from Java: evaluator.setExternalVariable(new QName("root"), new XdmAtomicValue("David"));

2 Comments

Can you explain this a little more? Was looking at the Saxon docs for java and not really clear how to use it. saxonica.com/html/documentation/javadoc/net/sf/saxon/ma/map/…
Thanks, didn't realise put all that in a multi-line string then compile.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.