0

I use @XmlJavaTypeAdapter to transform Map<Key, Value> objects into List<Value> when marshalling and the other way round when un-marshalling

Then the XML I obtain :

<map>
    <value>VALUE1</value>
...
</map>

My question is : how can I get rid of the surrounding tag in order to obtain

<value>VALUE1</value>

Bean Class

@XmlAccessorType(XmlAccessType.FIELD)
public class Data implements Serializable {

  /**
   * Constant for data value default name
   */
  public static final String DATA_VALUE_DEFAULT_NAME = "result";

  /**
   * Serial version UID
   */
  private static final long serialVersionUID = 7387937212735185585L;

  /**
   * key
   */
  @XmlAttribute
  private String key;

  /**
   * name
   */
  @XmlAttribute
  private String name;

  /**
   * Map of data
   */
  @XmlJavaTypeAdapter(MapDataAdapter.class)
  private Map<String, Data> dataMap;

  /**
   * Constructor
   */
  public Data() {

  }
}

Adapter

public class MapDataAdapter 
  extends XmlAdapter<MapDataAdapter.AdaptedDataMap,
                     Map<String, Data>> {

  /**
   * Adapted map
   */
  public static class AdaptedDataMap {

    /**
     * List of entry
     */
    @XmlElement(name = "data", required = true)
    protected List<Data> entry = new ArrayList<>();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Map<String, Data> unmarshal(
    AdaptedDataMap adaptedMap) throws Exception {
    if (adaptedMap == null) {
     return null;
    }
    Map<String, Data> map = new HashMap<>(adaptedMap.entry.size());
    for (Data entry : adaptedMap.entry) {
      map.put(entry.getKey(), entry);
    }
    return map;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public AdaptedDataMap marshal(
      Map<String, Data> map) throws Exception {
    if (map == null) {
      return null;
    }
    AdaptedDataMap adaptedMap = new AdaptedDataMap();
    for (Entry<String, Data> mapEntry : map.entrySet()) {
      adaptedMap.entry.add(mapEntry.getValue());
    }
    return adaptedMap;
  }
}

XML Output

<data key="12" name="TEST1">
    <dataMap>
        <data key="text" name="TEST2">
            <dataMap>
                <data key="azerty" name="TEST3">
                </data>
            </dataMap>
        </data>
    </dataMap>
</data>

What I need

<data key="12" name="TEST1">
    <data key="text" name="TEST2">
            <data key="azerty" name="TEST3">
            </data>
    </data>
</data>
Omarkad
  • 279
  • 3
  • 14

2 Answers2

0

JAXB map / with inheritance / without element wrapper

To marshall/unmarshall elements from/to a map without a element wrapper you can implement a List which stores the data in a Map. JAXB will see it as a simple list and will know how to handle it.

I developed this method because @XmlJavaTypeAdapter seems to be incompatible with @XmlElements or @XmlElementRefs. Actually, javadocs say @XmlJavaTypeAdapter can be used in conjunction with @XmlElementRefs but it did not worked for me. I kept catching a ClassCastException.

The alternative solved two problems at once:

  • does not require an element wrapper
  • allows @XmlElements mapping to be used

ExampleDocument.java

@XmlRootElement(name="example-document")
@XmlAccessorType(XmlAccessType.NONE)
public class ExampleDocument
{
  @XmlElements
  (
    {
      @XmlElement( name="child1" type=Child1.class ),
      @XmlElement( name="child2", type=Child2.class )
    }
  )
  private ChildrenList children = new ChildrenList();

  public Children getChildById( String id ) 
  { 
    return children.getMap().get( id ); 
  }
  public Collection<Children> allChildren()
  {
    return children.getMap().values();
  }
}

Children.java

@XmlTransient
public class Children
{
  private String id;
}

Child1.java

@XmlType
public class Child1 extends Children
{
}

Child2.java

@XmlType
public class Child2 extends Children
{
}

ChildrenList.java

public class ChildList implements List<Children>
{
  private Map<String,Children> map 
     = new TreeMap<String,Children>();

  @Override
  public boolean add( Children c ) 
  {
    return map.put( c.getId(), c ) != null;
  }
  @Override
  public boolean remove( Object o )
  {
    return map.remove( ((Children)o).getId() ) != null;
  }
  @Override
  public int size()
  {
    return map.size();
  }
  @Override
  public boolean isEmpty()
  {
    return map.isEmpty();
  }
  @Override
  public boolean contains( Object o )
  {
    return map.containsKey( ((Children)o).getId() );
  }
  @Override
  public Iterator<Children> iterator()
  {
    return map.values().iterator();
  }
  @Override
  public Object[] toArray()
  {
    return map.values().toArray();
  }
  @Override
  public <T> T[] toArray( T[] ts )
  {
    return map.values().toArray( ts );
  }
  @Override
  public boolean addAll( Collection<? extends ArticleBiblio> collection )
  {
    boolean result = false;
    for( ArticleBiblio b : collection )
    {
      result |= add( b );
    }
    return result;
  }

  /* all other methods throws UnsupportedOperationException */
}
Lucas
  • 258
  • 2
  • 5
0

I like Lucas' solution and expanded on it here by making it generic to prevent unnecessary type checking.

List Item Abstract Class

abstract class MapListItem<K> {
    /* Note that we can't implement this here, because we may want to rename or restructure the key attribute in the XML */
    public abstract K getKey();
}

Example Item Class

public class ItemA extends MapListItem<String>{
    @XmlAttribute(name="keyA")
    public String key;
    @XmlElement(name = "valueA")
    public ArrayList<ValueA> values = new ArrayList<>();

    public ItemA(String key) {
        this.key = key;
    }

    public ItemA(String key, ArrayList<ValueA> values) {
        this(key);
        this.values = values;
    }

    @Override
    public String getKey() {
        return key;
    }

}

Genericized Map List - K is key type, V is value type, which also contains a key.

public class MapList<K, V extends MapListItem<K>> implements List<V> {
    private Map<K, V> map = new TreeMap<>();

    public MapList(Collection<? extends V> collection) {
        addAll(collection);
    }

    @Override
    public boolean add(V c) {
        return map.put(c.getKey(), c) != null;
    }

    @Override
    public boolean remove(Object o) {
        return map.remove(((V) o).getKey()) != null;
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return map.containsKey(((V) o).getKey());
    }

    @Override
    public Iterator<V> iterator() {
        return map.values().iterator();
    }

    @Override
    public Object[] toArray() {
        return map.values().toArray();
    }

    @Override
    public <T> T[] toArray(T[] ts) {
        return map.values().toArray(ts);
    }

    @Override
    public boolean addAll(Collection<? extends V> collection) {
        boolean result = false;
        for (V b : collection) {
            result |= add(b);
        }
        return result;
    }
}

Now we can make a MapList like this:

@XmlElement(name = "itemA")
public MapList<String,ItemA> itemAs;
Gerry
  • 53
  • 6