1

So I have a couple of XMLs which I'm trying to unmarshal.

One XML can look like this:

<TABLE>
    <RECORDS>
        <RECORD>
            <DOC_ID>some value</DOC_ID>
            <ENTITY_ID>some value</ENTITY_ID>
            ...more entries
        </RECORD>
        <RECORD>
            <DOC_ID>some value</DOC_ID>
            <ENTITY_ID>some value</ENTITY_ID>
            ...more entries
        </RECORD>
    </RECORDS>
</TABLE>

Another XML can also look like this:

<TABLE>
    <RECORDS>
        <RECORD>
            <SUB_ID>some value</SUB_ID>
            <CASE_DOC_ID>some value</CASE_DOC_ID>
            ...more entries
        </RECORD>
        <RECORD>
            <SUB_ID>some value</SUB_ID>
            <CASE_DOC_ID>some value</CASE_DOC_ID>
            ...more entries
        </RECORD>
    </RECORDS>
</TABLE>

Each XML always has TABLE as root and RECORDS as a child and RECORD as grandchild just that the data in RECORD is different.

I don't want to marshal anything, just unmarshal and get the data.

Inside my Table Class I have

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "TABLE")
public class Table {

    @XmlJavaTypeAdapter(MapAdapter.class)
    private Map<String, String> RECORDS;

    public Map<String, String> getMap() {
        return RECORDS;
    }

    public void setMap(Map<String, String> record) {
        this.RECORDS = record;
    }

}

I took MapAdapter from this link: JAXB Marshal and Unmarshal Map to/from <key>value</key>

My issue is this: I would like to be able to get each Tag name in the record (DOC_ID/SUB_ID or whatever in the XML) and each of it's values as Strings when I pass in the XML but I'm not sure how to go about it.

Any help to point me in the right direction would be appreciated.

EDIT:

2 new questions! I realized I would probably need a list of Maps for all the records.

  • I managed to get the last record's keys and values using the Table class above. How do I turn it into a list of Maps so I can get each record instead of just the last?
  • Another question is the Table class only works if I don't have a RECORDS tag in my xml. How do I fix that?
Moses Koh
  • 13
  • 3

1 Answers1

0

You need to be able to annotate the Map with the MapAdapter from the link you provided, so you need to create a subclass.

@XmlJavaTypeAdapter(MapAdapter.class)
public class Record extends HashMap<String, String> {
}

Then your Table class is simple enough, though you need to annotate with @XmlElementWrapper to get the extra level of XML.

@XmlRootElement(name = "TABLE")
public class Table {
    private List<Record> records = new ArrayList<>();

    @XmlElementWrapper(name = "RECORDS")
    @XmlElement(name = "RECORD")
    public List<Record> getRecords() {
        return this.records;
    }
    public void setRecords(List<Record> records) {
        this.records = records;
    }
}

When working with JAXB and the parsing doesn't load all the information, I've found that it helps to first write code to generate XML, because you can then see, in the generated XML, how the JAXB mappings differ from what you expected/intended.

It's easier to create sample data if you add a few helper methods to the above classes, so lets do that first:

@XmlJavaTypeAdapter(MapAdapter.class)
public class Record extends HashMap<String, String> {
    private static final long serialVersionUID = 1L;

    public Record addElement(String name, String value) {
        put(name, value);
        return this;
    }

    @Override
    public String toString() {
        return "Record" + super.toString();
    }
}
@XmlRootElement(name = "TABLE")
public class Table {
    private List<Record> records;

    public Table() {
        this.records = new ArrayList<>();
    }
    public Table(Record... records) {
        this.records = Arrays.asList(records);
    }

    @XmlElementWrapper(name = "RECORDS")
    @XmlElement(name = "RECORD")
    public List<Record> getRecords() {
        return this.records;
    }
    public void setRecords(List<Record> records) {
        this.records = records;
    }

    @Override
    public String toString() {
        return "Table" + this.records.toString();
    }
}

Then we can easily write test code to see that it all works ok.

Build data to generate as XML

Table table1 = new Table(
        new Record().addElement("DOC_ID", "some value")
                    .addElement("ENTITY_ID", "some value"),
        new Record().addElement("SUB_ID", "some value")
                    .addElement("CASE_DOC_ID", "some value")
);
System.out.println(table1);
Table[Record{DOC_ID=some value, ENTITY_ID=some value}, Record{SUB_ID=some value, CASE_DOC_ID=some value}]

Generate XML

String xml;
try (StringWriter out = new StringWriter()) {
    JAXBContext jaxbContext = JAXBContext.newInstance(Table.class);
    Marshaller marshaller = jaxbContext.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.marshal(table1, out);
    xml = out.toString();
}
System.out.println(xml);
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TABLE>
    <RECORDS>
        <RECORD>
            <DOC_ID>some value</DOC_ID>
            <ENTITY_ID>some value</ENTITY_ID>
        </RECORD>
        <RECORD>
            <SUB_ID>some value</SUB_ID>
            <CASE_DOC_ID>some value</CASE_DOC_ID>
        </RECORD>
    </RECORDS>
</TABLE>

We can confirm that the JAXB mappings generate the XML format we intend to parse.

Parse XML

Table table2;
{
    JAXBContext jaxbContext = JAXBContext.newInstance(Table.class);
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    table2 = (Table) unmarshaller.unmarshal(new StringReader(xml));
}
System.out.println(table2);
Table[{DOC_ID=some value, ENTITY_ID=some value}, {SUB_ID=some value, CASE_DOC_ID=some value}]

As you can see, the parsed data has all the values, and it matches the original data.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thanks, I'll try it out. However just to note that DOC_ID and SUB_ID are in different XML files and that each record has around 20-30 tags so I didn't want to type them out manually. – Moses Koh Oct 19 '19 at 01:24
  • @MosesKoh This is just an examples that shows that a `` element can contain dynamically named sub-elements. Whether a single XML file can have disparate fields in multiple `` elements is besides the point. – Andreas Oct 19 '19 at 01:39
  • @MosesKoh If you have `@XmlAccessorType(XmlAccessType.FIELD)` (I don't), put the `@XmlElement` on the field. If you don't specify access type, put `@XmlElement` on the getter method (like I did), because access type defaults to `PUBLIC_MEMBER` (and the field is `private`, right?) – Andreas Oct 21 '19 at 00:52
  • whoops I forgot I added `@XmlAccessorType(XmlAccessType.FIELD)`. It's running now thanks. Now i need to figure out why my values come out as null when reading the file (I'm passing in a File type by the way). The keys are there but not the values. – Moses Koh Oct 21 '19 at 01:06