Search code examples
javaxmlnestedmaps

How deeply can I nest maps and is there a better way


I am creating an XML file with this structure

<test details>
    <test name>test 1</test name
        <test result>
            <test date>2023-01-03</test date>
                <test value>16</test value>
            <test date>2023-01-02</test date>
                <test value>18</test value>
            <test date>2023-01-01</test date>
                <test value>24</test value>
        </test result>
    <test name>test 2</test name>
        <test result>
            <test date>2023-01-03</test date>
                <test value>16</test value>
            <test date>2023-01-02</test date>
                <test value>18</test value>
            <test date>2023-01-01</test date>
                <test value>24</test value>
        </test result>
    </test name>
</test details>

My plan is to use maps for each depth of the nest. I imagine I can nest maps as deeply as necessary but iterating them must be a nightmare.

This is my thinking about the data structure required to create the XML

**test result** is a list of maps Map<date, value> results;
**test name** is a list of maps Map<key, Map of results> testNames;
**test details** is a list of maps Map<key, Map of test names> testDetails;

The maps don't necessarily need to be sorted other than the test dates need to be in descending chronological order. That said, my current thinking is to use TreeMap just too keep things tidy

My question is : Will I need a nested for loop 3 deep in order to iterate all the maps and is there a better data structure than a Map that will achieve the required output ?


Solution

  • Following up on Answer by Kay, the more sophisticated approach would be to define your own classes to represent this information rather than nesting maps.

    If the point of your custom classes is to communicate data transparently and immutably, then define your classes as records.

    The bottom of the hierarchy is a result, a date with a score.

    public record Result( LocalDate date , int score ) { }
    

    If you want to sort a collection of these Result objects, we must make the class implement Comparable.

    public record Result( LocalDate date , int score ) implements Comparable < Result >
    {
        private static final Comparator < Result > comparator =
                Comparator
                        .comparing ( Result :: date )
                        .thenComparing ( Result :: score );
    
        @Override
        public int compareTo ( Result o )
        {
            return Result.comparator.compare ( this , o );
        }
    }
    

    We collect those results for a particular named test. In Java 21+, use SequencedCollection; in earlier Java use NavigableSet.

    public record Test( String name , SequencedCollection < Result > results ) { }
    

    Gather these Test objects into a collection as the Detail class. We do not really need this level in our hierarchy within Java, if there are no other fields besides the collection of tests. But in XML there should be an outer element wrapping all the content; this class parallels that outer XML element.

    public record Detail( Collection < Test > tests ) { }
    

    Notice that I dropped your <test result> element. That elements creates a meaningless extra level in the hierarchy.

    By the way, I really do not like the names Test and Result. The first has a specific meaning in Java development. The second is commonly used in discussions. So using these as class and field names could be confusing. I suggest devising better, more meaningful names.

    Your result data were identical, so I changed them to differentiate samples.

    Test test1 =
            new Test (
                    "test 1" ,
                    new TreeSet <> (  // Use `TreeSet` as it implements `SequencedCollection`.
                            Set.of (
                                    new Result ( LocalDate.parse ( "2023-01-03" ) , 16 ) ,
                                    new Result ( LocalDate.parse ( "2023-01-02" ) , 18 ) ,
                                    new Result ( LocalDate.parse ( "2023-01-01" ) , 24 )
                            )
                    )
            );
    
    Test test2 =
            new Test (
                    "test 2" ,
                    new TreeSet <> (  // Use `TreeSet` as it implements `SequencedCollection`.
                            Set.of (
                                    new Result ( LocalDate.parse ( "2023-01-03" ) , 7 ) ,
                                    new Result ( LocalDate.parse ( "2023-01-02" ) , 42 ) ,
                                    new Result ( LocalDate.parse ( "2023-01-01" ) , 99 )
                            )
                    )
            );
    
    Detail detail =
            new Detail (
                    List.of (
                            test1 ,
                            test2
                    )
            );
    
    System.out.println ( "detail = " + detail );
    

    When run:

    detail = Detail[tests=[Test[name=test 1, results=[Result[date=2023-01-01, score=24], Result[date=2023-01-02, score=18], Result[date=2023-01-03, score=16]]], Test[name=test 2, results=[Result[date=2023-01-01, score=99], Result[date=2023-01-02, score=42], Result[date=2023-01-03, score=7]]]]]


    If parsing/generating XML via Jakarta XML Binding 4.0 or earlier, you will need to transform those record classes into conventional classes. The next version of Jakarta EE Platform 11 will support records, so I expect Jakarta XML Binding will too then.