Tag: XML to Java Objects

  • Transforming XML to objects using XStream

    I was working on some functionality that required transforming information from XML format to a Java object. XStream is a library that can do such a job. However, I really struggled to find a good and straight forward tutorial on how to transform a common XML document structure.

    Suppose you have a structure like this. There are four types of nodes here:

    • “Attribute-only”
      • mailHost
      • parameter
    • “Attribute-and-children”
      • reportsList
      • report
    • “Text-only”
      • recipient
    • “Children-only”
      • recipients
      • parameters
    <?xml version=”1.0″ encoding=”UTF-8″?>
    <reportEmailer>
    <mailHost host=”some host”
    port=”123456″
    nameFrom=”John”
    emailFrom=”john@123.com”
    password=”somesecurepassword”>
    </mailHost>
    <reportsList saveReportIn=”C:\Reports”>
    <report subject=”subject”
    name=”name”
    reportPath=”/com/test/reports/cashclosed”
    schedule=”0/30 * * * * ?”
    bodyText=”bodyText”>
    <recipients>
    <recipient>owner@store.com</recipient>
    </recipients>
    <parameters>
    <parameter type=”dateStart” year=”2009″ month=”4″ dayOfMonth=”3″ hour=”0″ minute=”0″></parameter>
    <parameter type=”dateEnd” year=”2011″ month=”4″ dayOfMonth=”3″ hour=”0″ minute=”0″></parameter>
    </parameters>
    </report>
    </reportsList>
    </reportEmailer>

    The ideal situation would be one where node’s attributes are mapped to String  variables and children nodes are mapped to collection variables. For example, the object for the “report” node should be as follows:

    Report.java

    String subject;

    String name;

    List lstRecipients<String>

    List lstParameters<Parameter>

    Notice that the collection variables names are different than the nodes names, this is because I’m trying to follow java beans standard here, and it’s actually an issue when working with XStream, since it’s reflection features can match nodes and variable names. I will assume that both names are different, so that he following solution can work in both cases.

    So, to get the desired object graph we need to perform the following steps:

    • Create a mapping to match nodes with specific classes. (in XStream, this is called aliasing)
    • Specify which variables are attributes
    • Specify implicit collections. When creating the object graph, XStream may assume some nodes are irrelevant, and exclude them from the object graph. This would be the natural behaviour for example in the case of the “reportsList” node, where unless we specify it as an implicit collection, XStream will not add it to the object graph and instead just add a collection of “report” objects, therefore causing us to lose the “saveReportIn” attribute.
    • Specify fields. This is needed with the “children-only” types of node. It would be a waste to create objects for the “recipients” and “parameters” node since they have no information. Instead, we would prefer that the “report” object has one collection for each.

    The resulting code is:

    XStream xstream = new XStream(new DomDriver());

    //Map the root node to a class
    xstream.alias(“reportEmailer”, ReportEmailerVO.class);

    //Tell XStream that the “saveReportIn” variable in ReportsVO is an attribute in XML
    xstream.useAttributeFor(ReportsVO.class, “saveReportIn”);
    //When adding implicit collections we are telling XStream the following:
    //ReportEmailerVO has a variable “listaReportes” that maps to the “reportsList” node, which is represented by the “ReportsVO” object
    xstream.addImplicitCollection(ReportEmailerVO.class, “listaReportes”, “reportsList”, ReportsVO.class);

    xstream.useAttributeFor(MailHostVO.class, “host”);
    xstream.useAttributeFor(MailHostVO.class, “port”);
    xstream.useAttributeFor(MailHostVO.class, “nameFrom”);
    xstream.useAttributeFor(MailHostVO.class, “emailFrom”);
    xstream.useAttributeFor(MailHostVO.class, “password”);

    //ReportsVO has a variable “lstReports” that maps to the “report” node, which is represented by the “ReportVO” object
    xstream.addImplicitCollection(ReportsVO.class, “lstReports”, “report”, ReportVO.class);

    xstream.useAttributeFor(ReportVO.class, “subject”);
    xstream.useAttributeFor(ReportVO.class, “name”);
    xstream.useAttributeFor(ReportVO.class, “reportPath”);
    xstream.useAttributeFor(ReportVO.class, “schedule”);
    xstream.useAttributeFor(ReportVO.class, “bodyText”);

    //The “recipients” node should map to the “lstRecipients” variable of ReportVO
    xstream.aliasField(“recipients”, ReportVO.class, “lstRecipients”);
    //The “recipient” node is of type String
    xstream.alias(“recipient”, String.class);

    //The “parameters” node should map to the “lstParameters” variable of ReportVO
    xstream.aliasField(“parameters”, ReportVO.class, “lstParameters”);
    //The “parameter” node is of type “ReportParameterVO”
    xstream.alias(“parameter”, ReportParameterVO.class);
    xstream.useAttributeFor(ReportParameterVO.class, “type”);
    xstream.useAttributeFor(ReportParameterVO.class, “year”);
    xstream.useAttributeFor(ReportParameterVO.class, “month”);
    xstream.useAttributeFor(ReportParameterVO.class, “dayOfMonth”);
    xstream.useAttributeFor(ReportParameterVO.class, “dayOfWeek”);
    xstream.useAttributeFor(ReportParameterVO.class, “hour”);
    xstream.useAttributeFor(ReportParameterVO.class, “minute”);

    Notice that we use the aliasField methods to avoid creating a “Parameters” and “Recipients” classes. They would add no value so it would be a waste to create them just to support this XML structure.