JAXB удаляет ненужный вложенный тег XML

В настоящее время у меня есть следующий вывод из моей программы:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<container>
    <elements>
        <property name="e1">
            <foo name="Alex" status="Married"/>
        </property>
        <property name="e2">
            <foo name="Chris" status="Married with 2 children"/>
        </property>
    </elements>
</container>

Как видите, наличие тегов <container> и <elements> бесполезно. Я хотел бы удалить <elements>, поэтому вывод будет выглядеть так:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<container>
    <property name="e1">
        <foo name="Alex" status="Married"/>
    </property>
    <property name="e2">
        <foo name="Chris" status="Married with 2 children"/>
    </property>
</container>

Код, который генерирует первый вывод, указан ниже:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Container {

    @XmlElement
    @XmlJavaTypeAdapter(MyAdapter.class)
    private Map<String, Foo> elements = new HashMap<String, Foo>();

    public Container() {
        this.elements = new HashMap<String, Foo>();
    }

    public Map<String, Foo> getElements() {
        return elements;
    }

    @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
    @XmlRootElement(name = "foo")
    static class Foo {
        @XmlAttribute
        public String name;

        @XmlAttribute
        public String status;

        public Foo(String name, String status) {
            this.name = name;
            this.status = status;
        }

        public Foo() {
        }
    }

    public static void main(String[] args) throws JAXBException {
        final JAXBContext context = JAXBContext.newInstance(Container.class);
        final Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        final Container c = new Container();
        final Map<String, Foo> elementsMap = c.getElements();
        elementsMap.put("e1", new Foo("Alex", "Married"));
        elementsMap.put("e2", new Foo("Chris", "Married with 2 children"));

        m.marshal(c, System.out);
    }
}

И класс MyAdapter, основанный на JAXB @XmlAdapter: Карта -> Адаптер списка? (только Маршалл) :

public class MyAdapter extends XmlAdapter<MyAdapter.AdaptedFoo, Map<String, Foo>> {

    static class AdaptedFoo {
        public List<Property> property = new ArrayList<>();
    }

    public static class Property {
        @XmlAttribute
        public String name;

        @XmlElementRef(type = Foo.class)
        public Foo value;
    }

    @Override
    public Map<String, Foo> unmarshal(AdaptedFoo v) throws Exception {
        return null;
    }

    @Override
    public AdaptedFoo marshal(Map<String, Foo> map) throws Exception {
        if (null == map) {
            return null;
        }
        AdaptedFoo adaptedFoo = new AdaptedFoo();
        for (Entry<String, Foo> entry : map.entrySet()) {
            Property property = new Property();
            property.name = entry.getKey();
            property.value = entry.getValue();
            adaptedFoo.property.add(property);
        }
        return adaptedFoo;
    }
}

Как я могу удалить тег <elements> из моего вывода?


edit: Я нашел "грязный" способ сделать это - установить @XmlRootElement(name = "") для класса Container. Но есть ли какой-нибудь «элегантный» способ?


person Greg Witczak    schedule 11.08.2014    source источник
comment
Вместо того, чтобы адаптировать Map к объекту-оболочке, имеющему List<Property>, пытались ли вы адаптировать его напрямую к List<Property> без промежуточного AdaptedFoo?   -  person Ian Roberts    schedule 15.08.2014


Ответы (2)


XML-элемент <elements> необходим для связи вложенной последовательности элементов со свойством Map<?,?> elements. Вы не можете его отбросить: откуда немаршаллеру знать, где находятся элементы <property> *на уровне элемента <container>.

Наличие List<Property> отличается, поскольку JAXB обрабатывает повторяющиеся элементы x, «зашитые» как List<?> x, поэтому нет необходимости в оболочке.

Поскольку вы пишете классы Java с аннотациями, вы можете использовать это, добавив (в контейнер) еще одно «виртуальное» поле и изменив некоторые аннотации:

@XmlAccessorType(XmlAccessType.PROPERTY)
public class Container {
// ...
@XmlTransient    
public Map<String,Foo> getElements(){
    return elements;
}
private List<Property> property;
@XMLElement
public List<Property> getProperty(){
    List<Property>  props = elements.entrySet().stream()
                            .map( e -> new Property( e.getKey(), e.getValue() )
                            .collect( Collectors.toList() );
    return props;
}

Старые Java могут подойти

   List<Property> props = new ArrayList<>();
   for( Map.Entry<String,Foo> e: elements.entrySet() ){
       props.add( new Property( e.getKey(), e.getValue() ) );
   }
   return props;

Вот такие маршалы:

<container>
    <property name="aaa">
       <foo name="aaaname" status="aaastatus"/>
    </property>
    <property name="bbb">
       <foo name="bbbname" status="bbbstatus"/>
    </property>
</container>

Это не демаршалирует!

person laune    schedule 11.08.2014
comment
Я забыл упомянуть, что использую Java 7 - .stream() является частью более новой Java. Не могли бы вы переписать код для более старой версии Java? - person Greg Witczak; 12.08.2014
comment
Извините, должно быть, пропустил ваш комментарий - добавлен старый код. - person laune; 15.08.2014

Помимо удаления тега путем «взлома» имени элемента, чтобы оно было «», я не думаю, что вы сможете обойти использование этого типа данных без вложенного тега.

Тег описывает тип данных, который охватывает ваш адаптированный класс, который вы аннотировали как элемент XML с именем elements, который содержит свойства, определенные вашим адаптером.

Если вы хотите что-то, что более точно соответствует тому, что, как я думаю, вам нужно, либо измените контекст вашего корневого класса, либо, возможно, используйте List вместо HashMap в своем классе-контейнере.

person Ryan J    schedule 11.08.2014
comment
Map сопоставляется с List, проверьте адаптер, который я написал. Что значит изменить контекст вашего корневого класса? - person Greg Witczak; 12.08.2014
comment
Я понимаю, что ваша карта сопоставлена ​​с реализацией списка (именно это дает вам желаемое поведение внутри тегов элементов), ваша проблема в том, что карта в вашем корневом классе имеет связанный с ней корневой элемент, поэтому вы получаете тег, который вы не хотите. Под изменением контекста вашего корневого класса я подразумеваю следующее: выясните, является ли Container подходящим для него именем. Это лишнее? У Container может быть много вещей, одна из которых elements. - person Ryan J; 12.08.2014