abra
abra

Reputation: 575

Unmarshall XML file to Object with DSM (Declarative Stream Mapping)

I need to convert XML to Java objects. I chose Declarative Stream Mapping (DSM) for this, but it is not clear how to map the nested element with attributes to the list for me. Specifically, how to describe in the YAML file for the next piece:

<categories>
  <category id="1" parentId="2">Lorum Ipsum</category>
</categories>

I tried all the options, but they were all unsuccessful.

Below are the pieces of code that I am running.

DSM (yaml):

  result:
  type: object
  path: /catalog/shop
  fields:
    name:
      path: name
    company:
      path: company
    url:
      path: url
    platform:
      path: platform
    agency:
      path: agency
    email:
      path: email
    currencies:
      type: array
      path: currencies/currency
      fields:
        id:
          xml:
            attribute: true
        rate:
          xml:
            attribute: true
    categories:
      type: array
      path: categories/category
      fields:
        id:
          xml:
            attribute: true
        parentId:
          xml:
            attribute: true
    deliveryOptions:
      type: array
      path: delivery-options/option
      fields:
        cost:
          xml:
            attribute: true
        days:
          xml:
            attribute: true
    offers:
      type: array
      path: offers/offer
      fields:
        id:
          xml:
            attribute: true
        available:
          xml:
            attribute: true
        type:
          xml:
            attribute: true
        param:
          path: param
        offer:
          fields:
            url:
              path: url
            price:
              path: price
# shortened due to length

XML file to convert:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE yml_catalog SYSTEM "shops.dtd">
    <catalog date="2019-09-08 22:37">
      <shop>
        <name>company name</name>
        <company>Microsoft</company>
        <url>https://microsoft.com</url>
        <platform>woefjwoefi</platform>
        <agency>Agent</agency>
        <email>[email protected]</email>
        <currencies>
          <currency id="USD" rate="1"/>
        </currencies>
        <categories>
          <category id="398" parentId="198">Lorum Ipsum</category>
          <category id="409">Uncategorized</category>
        </categories>
        <offers>
          <offer id="2016" available="true" type="vendor.model">
            <url>https://ms.com/id/1</url>
            <price>35.00</price>
            <currencyId>USD</currencyId>
            <categoryId>168</categoryId>
            <picture>https://ms.com/image/cache/catalog/8987-600x600.jpg</picture>
            <store>true</store>
            <pickup>true</pickup>
            <delivery>true</delivery>
            <vendor>MS</vendor>
            <vendorCode>MS-10111</vendorCode>
            <model>plasdf</model>
            <downloadable>false</downloadable>
            <param name="Weight">0.0</param>
          </offer>
          <offer id="2017" available="true" type="vendor.model">
            <url>https://ms.com/id/2</url>
            <price>250.00</price>
            <currencyId>USD</currencyId>
            <categoryId>201</categoryId>
            <picture>https://ms.com/image/cache/catalog/aktiv-600x600.png</picture>
            <store>true</store>
            <pickup>true</pickup>
            <delivery>true</delivery>
            <vendor>MS</vendor>
            <vendorCode>MS03238</vendorCode>
            <model>woeifjwoef wefw fw we</model>
            <description>wiefwof wef</description>
            <downloadable>false</downloadable>
            <weight>30.0</weight>
            <param name="Weight">30.0</param>
          </offer>
        </offers>
      </shop>
    </catalog>

Java:

public class Shop {
  private String name;
  private String company;
  private String url;
  private String platform;
  private String agency;
  private String email;
  private List<Currencies> currencies;
  private List<Categories> categories;
  private List<DeliveryOptions> deliveryOptions;
  private List<Offers> offers;
  // getters/setters
}

class Currencies {
  // getters/setters
}

class Categories {
  // getters/setters
}

class DeliveryOptions {
  // getters/setters
}

class Offers {
  // getters/setters
}

Output:

{
  "name" : "company name",
  "company" : "Microsoft",
  "url" : "https://microsoft.com",
  "platform" : "woefjwoefi",
  "agency" : "Agent",
  "email" : "[email protected]",
  "currencies" : null,
  "categories": null,
  "deliveryOptions": null,
  "offers": null
}

Upvotes: 0

Views: 352

Answers (1)

delephin
delephin

Reputation: 1115

I made some slight indentation adjustement and your dsm config file worked just fine.

DSM.yml

result:
  type: object
  path: /catalog/shop
  fields:
    name:
      path: name
    company:
      path: company
    url:
      path: url
    platform:
      path: platform
    agency:
      path: agency
    email:
      path: email
    currencies:
      type: array
      path: currencies/currency
      fields:
        id:
          xml:
            attribute: true
        rate:
          xml:
            attribute: true
    categories:
      type: array
      path: categories
      xml:
        path: categories/category
      fields:
        name:
          path: ./
        id:
          xml:
            attribute: true
        parentId:
          xml:
            attribute: true
    deliveryOptions:
      type: array
      path: delivery-options/option
      fields:
        cost:
          xml:
            attribute: true
        days:
          xml:
            attribute: true
    offers:
      type: array
      path: offers/offer
      fields:
        id:
          xml:
            attribute: true
        available:
          xml:
            attribute: true
        type:
          xml:
            attribute: true
        param:
          path: param
        url: default
        price: default
        currencyId: default
        categoryId: default

output:

{
  "name" : "company name",
  "company" : "Microsoft",
  "url" : "https://microsoft.com",
  "platform" : "woefjwoefi",
  "agency" : "Agent",
  "email" : "[email protected]",
  "currencies" : [ {
    "id" : "USD",
    "rate" : 1
  } ],
  "categories" : [ {
    "id" : 398,
    "parentId" : 198,
    "name" : "Lorum Ipsum"
  }, {
    "id" : 409,
    "parentId" : null,
    "name" : "Uncategorized"
  } ],
  "deliveryOptions" : null,
  "offers" : [ {
    "id" : "2016",
    "available" : true,
    "type" : "vendor.model",
    "url" : "https://ms.com/id/1",
    "price" : 35.0,
    "currencyId" : "USD",
    "categoryId" : "168",
  }, {
    "id" : "2017",
    "available" : true,
    "type" : "vendor.model",
    "url" : "https://ms.com/id/2",
    "price" : 250.0,
    "currencyId" : "USD",
    "categoryId" : "201",

  } ]
}

This is the code I'm using to test it:

@Test
public void dsmParsing() throws IOException {

    InputStream is = new FileInputStream("src/main/resources/dsm.yml");

    DSMBuilder builder = new DSMBuilder(is);
    DSM dsm = builder.setType(DSMBuilder.TYPE.XML).create(Shop.class);
    Shop shop = (Shop) dsm.toObject(new File("src/main/resources/shop.xml"));

    ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();

    System.out.println(ow.writeValueAsString(shop));
}

To answer your question about nested elements with attributes, you have to take into account that when you have this definition:

categories:
  type: array
  path: categories/category

when you define the fields you will be describing the element <category>.

In the example below, attributes id and parentId will be defined in the fields segment as attributes, but to get the value of the actual category element, you'll need to reference the current path with ./. And if you had subelements, you just need to add those subelements in the fields segment.

xml

 <categories>
   <category id="398" parentId="198">Lorum Ipsum</category>
 </categories>

dsm

categories:
  type: array
  path: categories/category
  fields:
    name: ## you could call it 'value' or whatever you want
      path: ./
    id:
      xml:
        attribute: true
    parentId:
      xml:
        attribute: true

This next example is quite interesting:

xml

<offers attr1="abc" attr2="def">
    <offer id="2016" available="true">
        <url>https://ms.com/id/1</url>
    </offer>
</offers>

First, the you'll need another pojo to correctly represent offers and its attributes:

Shop

public class Shop {
  ...
  OffersList offers; // this attribute's name needs to match the one used in the dsm descriptor
}

New class OffersList

public class OffersList {
  String attr1;
  String attr2;
  List<Offers> offer; // this attribute's name needs to match the one used in the dsm descriptor
}

Finally the dsm will look like:

offers: ## name should match with Shop's attribute 'offers' (OffersList)
  type: object ## now offers is an object, is mandatory to define 'fields'
  path: offers  ## focus is on the <offers> element
  fields:
    attr1:
      xml:
        attribute: true
    attr2:
      xml:
        attribute: true
    offer:   ## name should match with OffersList's attribute 'offer'
      type: array
      path: ../offers/offer  ## will go back a level to indicate <offers> <offer> should be parsed as an array
      fields:
        id:
          xml:
            attribute: true
        available:
          xml:
            attribute: true
        url:
          path: url

output

  "offers" : {
    "attr1" : "abc",
    "attr2" : "def",
    "offer" : [ {
      "id" : "2016",
      "available" : true,
      "url" : "https://ms.com/id/1",
    } ]
  }

Upvotes: 1

Related Questions