Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XStream: how can I hide 2 unnecessary parent nodes in marshaled XML?

In a unit test project, I need some help figuring out how how I can hide 2 unnecessary parent nodes in marshaled XML? Can anyone help me figure out how to do it?

My marshalled output looks like this:

<suite>
  <suiteName>Suite 1</suiteName>
  <sauceURL>http://username-string:[email protected]:80/wd/hub</sauceURL>
  <tests>
    <test>
      <rowArgs>
        <arg>
          <enabled type="java.lang.Boolean">true</enabled>
        </arg>
        <arg>
          <testname type="java.lang.String">Test 1</testname>
        </arg>
        <arg>
          <environment type="java.lang.String">portal1</environment>
        </arg>
        <arg>
          <testlocale type="java.lang.String">Grid</testlocale>
        </arg>
        <arg>
          <browser type="java.lang.String">Firefox</browser>
        </arg>
        <arg>
          <url type="java.lang.String">http://google.com</url>
        </arg>
      </rowArgs>
    </test>
            ...

But I want to hide the unnecessary "rowArgs" and "arg" tag so that the the remaining nodes are immediately under the test node. How can I do this?

You can see my code HERE, if it helps you to help me. I suspect I need to write my own transformer? I'll work on that experiment in the meanwhile.

like image 631
djangofan Avatar asked Dec 15 '13 23:12

djangofan


1 Answers

Indeed you will need a custom converter for this as it is two nested collection which you do not want to show and those objects might have other child elements which would be serialized.

This is a case not directly covered by XStream, but easily fixed with a custom converter.

You can find all this in a git clone of your project which contains all those modifications. But as everything should be visible here, I subsequently explain the key portions to the question including some code examples.

I implemented such a converter for your project. The code doing the conversion is this:

  @Override
  public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
    TestRow test = (TestRow)source;
    for (TestArguments arg : test.getArguments()) {
      for (ArgObject val : arg.getAllTestArguments()) {
        writer.startNode(val.getKey());
        writer.addAttribute("type", val.getType());
        writer.setValue(val.getVal());
        writer.endNode();

      }
    }
  }

  @Override
  public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    TestArguments testargs = new TestArguments();
    while (reader.hasMoreChildren()) {
      reader.moveDown();
      String typeval = reader.getAttribute("type");
      if (typeval.isEmpty()) {
        typeval = "null";
      }
      testargs.getAllTestArguments().add(new ArgObject(reader.getNodeName(), typeval, reader.getValue()));
      reader.moveUp();
    }
    TestRow result = new TestRow(testargs);
    return result;
  }

This will serialize all ArgObjects to xml with the flat node structure you defined in your ArgConverter. The deserialization creates the object back from this data.

You had two bugs in your source code which prevented deserialization:

  • XStream deserialization does not recognize all alias Annotations. It had problems with your root element SuiteData (aliased to suite). For this I added a aliad to the XStream object like this xStream.alias("suite", SuiteData.class); in your XMLDataHelper.
  • If you create a TestArguments instance with a default constructor (as you'll have to through deserialization), you call reset which assigns your argsWrapper member to null which then makes adding arguments quite impossible. I fixed that be newly initializing that member variable in the reset method (argsWrapper = new ArrayList<ArgObject>();).

As far as I understand your code this works as expected for serialization / deserialization. I wrote a small test program to force this process in each way and it seems to produce the same result:

public class XMLDataHelperTest {

  public static void main(String[] args) {
    File file = new File(System.getProperty("user.dir"), "test.xml");
    if (file.isFile()) {
      assertTrue(file.delete());
    }

    // write it once
    XMLDataHelper helper = new XMLDataHelper(file.getAbsolutePath());

    // read it once
    helper = new XMLDataHelper(file.getAbsolutePath());
    System.out.println(file);
  }
}
like image 96
Matthias Avatar answered Nov 10 '22 09:11

Matthias