Mumson
Mumson

Reputation: 37

Map inside POJO to XML with JAXB

I have POJO of product like this:

@XmlRootElement(name = "o")
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {
    @XmlAttribute
    private Integer avail;
    @XmlAttribute
    private  Long id;
    @XmlAttribute
    private BigDecimal price;
    @XmlAttribute
    private String url;
    @XmlAttribute
    private Integer stock;
    private String category;
    private String title;
    private String description;
    @XmlElementWrapper(name = "attrs")
    @XmlElement(name = "a")
    private Map<String, String> attributes;

//getters, setters, toString
}

Next class gives me list of products.

@XmlRootElement(name = "offers")
@XmlAccessorType(XmlAccessType.FIELD)
public class Products {

    @XmlElement(name = "o")
    private List<Product> products = null;

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }
}

Next class gets data from database and return it as a product list:

    private Connection conn = null;
    private ResultSet rs = null;
    private PreparedStatement selectProducts = null;
    private Query query = new Query();
    private Map<String, String> attr = new HashMap<String , String>();
    private List<Product> mainList = new ArrayList<>();
    private DescCreator descCreator = new DescCreator(); //taking care of product html description
    public List<Product> listFiller() {

        try {
            conn = DriverManager.getConnection(connectionData);
            selectProducts = conn.prepareCall(query.getMainQuery());
            rs = selectProducts.executeQuery();

            while(rs.next()){
                Product product = new Product();
                product.setId(rs.getLong("products_id"));
                product.setPrice(rs.getBigDecimal("products_price_tax"));
                product.setUrl("https://www.link.com/" + rs.getString("products_id") + ".html");
                product.setAvail(rs.getInt("products_availability_id"));
                product.setStock(rs.getInt("products_quantity"));
                product.setCategory(rs.getString("categories_name"));
                product.setTitle(rs.getString("products_name"));
                product.setDescription(descCreator.descript(rs.getString("products_description")));
                attr.put(rs.getString("products_extra_fields_name"), rs.getString("products_extra_fields_value"));
                product.setAttributes(attr);
                mainList.add(product);
            }

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return mainList;
    }

Class to populate main list (my list was built from other smaller lists).

@XmlRootElement(name = "offers")
@XmlAccessorType(XmlAccessType.PROPERTY)
public class listInitializer {
           
       //code to create main list from other smaller list

        return mainList;
    }
}

And main marshal class:

public class MainMarshal{
    private MainListInitializer mainListInitializer = new MainListInitializer();
    private Products products = new Products();

    public void marshalMain() throws JAXBException, IOException {

        products.setProducts(mainListInitializer.generateMainXml());

        JAXBContext jaxbContext = JAXBContext.newInstance(Products.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        jaxbMarshaller.marshal(products, new File("filepath"));
    }

}

Result of my code is something like this:

<offers>
  <o avail="1" id="1234" price="1234.00" url="some url" stock="1234">
    <category>PC</category>
    <title>Some product title</title>
    <desc>Some product description</desc>
    <attrs>
      <entry>
        <key>Map key</key>
        <value>Map value</value>
      </entry>
      <entry>
        <key>Map key</key>
        <value>Map value</value>
      </entry>
      <entry>
        <key>Map key</key>
        <value>Map value</value>
      </entry>
    </atrts>
  </o>
</offers>

And i need this as a result:

<offers>
  <o avail="1" id="1234" price="1234.00" url="some url" stock="1234">
    <category>PC</category>
    <title>Some product title</title>
    <desc>Some product description</desc>
    <attrs>
      <a name="Map key">Map value</a>
      <a name="Map key">Map value</a>
      <a name="Map key">Map value</a>
      <a name="Map key">Map value</a>
    </atrts>
  </o>
</offers>

Any suggetions or guides how to achiev this result? Is there any way to change tag names: entry, set and value?

Upvotes: 2

Views: 906

Answers (1)

Alexandra Dudkina
Alexandra Dudkina

Reputation: 4462

For this purpose you'll need to use XmlAdapter.

Following beans should be created:

public class Entry {

    @XmlAttribute
    public String name;
    
    @XmlValue
    public String value;

}

public class MapWrapper {

    @XmlElement(name="a")
    public List<Entry> entry = new ArrayList<>();

}

XML Adapter:

import java.util.Map;
import java.util.TreeMap;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>> {
    
    @Override
    public Map<String, String> unmarshal(MapWrapper adaptedMap) throws Exception {
        Map<String, String> map = new TreeMap<>();
        adaptedMap.entry.forEach(entry -> {
            map.put(entry.key, entry.value);
        });
        return map;
    }

    @Override
    public MapWrapper marshal(Map<String, String> map) throws Exception {
        MapWrapper adaptedMap = new MapWrapper();
        map.entrySet().stream().map(mapEntry -> {
            Entry entry = new Entry();
            entry.key = mapEntry.getKey();
            entry.value = mapEntry.getValue();
            return entry;
        }).forEachOrdered(entry -> {
            adaptedMap.entry.add(entry);
        });
        return adaptedMap;
    }

}

In class Product you'll need to change annotations for attributes map:

@XmlElement(name="attrs")
@XmlJavaTypeAdapter(MapAdapter.class)
private Map<String, String> attributes;

That will give following output:

<attrs>
    <a name="key">value</a>
</attrs>

Upvotes: 1

Related Questions