JAXB/MOXy: не вызывать установщик XmlElementWrapper при отсутствии элемента?

У меня есть установщик списка в классе, который аннотирован как @XmlElementWrapper(name = "foos"), так и @XmlElement(name = "foo").

Когда я распаковываю XML, в котором нет элементов ‹foos>‹/foos> или ‹foo/>, вызывается сеттер и передается пустой список. Есть ли способ получить следующее?:

  • Когда нет ‹foos/>, не вызывайте сеттер. Или, если необходимо вызвать сеттер, передайте null.
  • Когда ‹foos/> присутствует, но пуст, передать пустой список сеттеру.
  • Если ‹foos> имеет один или несколько дочерних элементов ‹foo/>, передать заполненный список.

person steamer25    schedule 19.05.2011    source источник
comment
Является ли этот класс корнем вашего дерева или представляет собой дочерний узел?   -  person bdoughan    schedule 20.05.2011
comment
... на самом деле, я бы хотел использовать один и тот же шаблон как в корневом, так и в дочернем случае.   -  person steamer25    schedule 20.05.2011


Ответы (4)


Вы можете использовать XmlAdapter для этого варианта использования:

input1.xml

Когда нет, не вызывайте сеттер. Или, если необходимо вызвать сеттер, передайте null.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <child/>
</root>

input2.xml

Когда присутствует, но пуст, передать пустой список установщику.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <child>
        <foos/>
    </child>
</root>

input3.xml

При наличии одного или нескольких дочерних элементов передать заполненный список.

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <child>
        <foos>
             <foo>Hello World</foo>
       </foos>
   </child>
</root>

Корень

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
public class Root {

    private Child child;

    @XmlJavaTypeAdapter(ChildAdapter.class)
    public Child getChild() {
        return child;
    }

    public void setChild(Child child) {
        this.child = child;
    }

}

Ребенок

import java.util.List;

public class Child {

    private List<String> strings;

    public List<String> getStrings() {
        return strings;
    }

    public void setStrings(List<String> strings) {
        System.out.println("setStrings");
        this.strings = strings;
    }

}

Дочерний адаптер

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class ChildAdapter extends XmlAdapter<ChildAdapter.AdaptedChild, Child> {

    public static class AdaptedChild {
        public Foos foos;
    }

    public static class Foos {
        public List<String> foo;
    }

    @Override
    public Child unmarshal(AdaptedChild adaptedChild) throws Exception {
        Child child = new Child();
        Foos foos = adaptedChild.foos;
        if(null != foos) {
            List<String> foo = foos.foo;
            if(null == foo) {
                child.setStrings(new ArrayList<String>());
            } else {
                child.setStrings(foos.foo);
            }
        }
        return child;
    }

    @Override
    public AdaptedChild marshal(Child child) throws Exception {
        AdaptedChild adaptedChild = new AdaptedChild();
        List<String> strings = child.getStrings();
        if(null != strings) {
            Foos foos = new Foos();
            foos.foo = strings;
            adaptedChild.foos = foos;
        }
        return adaptedChild;
    }

}

Демо

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        Object o;

        o = unmarshaller.unmarshal(new File("input1.xml"));
        marshaller.marshal(o, System.out);

        o = unmarshaller.unmarshal(new File("input2.xml"));
        marshaller.marshal(o, System.out);

        o = unmarshaller.unmarshal(new File("input3.xml"));
        marshaller.marshal(o, System.out);
    }

}

Вывод

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <child/>
</root>
setStrings
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <child>
      <foos/>
   </child>
</root>
setStrings
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <child>
      <foos>
         <foo>Hello World</foo>
      </foos>
   </child>
</root>
person bdoughan    schedule 20.05.2011
comment
Спасибо за быстрый ответ! Я попробую и дам вам знать. - person steamer25; 20.05.2011
comment
Хрм. Ситуация усложняется: дочерние объекты сами находятся в списках. Также дочерний класс имеет большое количество унаследованных свойств. Нужно ли их все дублировать и ретранслировать в адаптированной версии? - person steamer25; 20.05.2011

Это адаптер, который в конечном итоге сработал для осложнений, упомянутых в комментариях к ответу Блейза Догана:

public class ListOfFooAdapter extends XmlAdapter<ListOfFooAdapter.Adapted, List<Foo>> {
    @XmlRootElement(name = "foos")
    public static class Adapted {
        public List<Foo> foo;
    }

    @Override
    public List<Foo> unmarshal(Adapted adapted) throws Exception {
        return adapted.foo;
    }

    @Override
    public Adapted marshal(List<Foo> foo) throws Exception {
    if (null == foo) {
            return null;
        } else {
            Adapted adapted = new Adapted();
            adapted.foo = foo;
            return adapted;
        }
    }
}

... метод unmarshall не вызывается, если элемент не присутствует в XML.

Я аннотировал свое свойство списка следующим образом:

@XmlJavaTypeAdapter(ListOfFooAdapter.class)
public List<Foo> getFoos() {
    ...
}

public void setFoos(List<Foo> l) {
    ...
}
person steamer25    schedule 20.05.2011

Я пытался решить идентичную проблему и довольно долго играл с образцами здесь. Большое спасибо за них, довольно познавательно.

Однако мне казалось неправильным, что нечто настолько зрелое, как реализация JAXB в JDK 6, понимает элементы в XML и бессердечно передает мне код пустого списка.

Оказывается, JAXB заполнил список после вызова моего установщика со ссылкой на все еще пустой список, так что, когда мой код установщика выглядел так, он пропускал последующие обновления списка в течение оставшейся части немаршалинговая фаза:

@XmlElementWrapper(name = "foos")
@XmlElement(name = "foo")
public void setFoos(List<Foo> newFoos) {
    this.foos.clear();
    this.foos.addAll(newFoos);
}

Когда я изменил свой сеттер на

@XmlElementWrapper(name = "foos")
@XmlElement(name = "foo")
public void setFoos(List<Foo> newFoos) {
    this.foos = newFoos;
}

это сработало просто отлично. Всегда неприятно держаться за чужой указатель на список, потому что владелец списка может изменить его постфактум, но в данном случае именно это изменение устранило проблему.

Я подтвердил свое предположение, перебрав список, переданный JAXB, и, конечно же, он пожаловался на ConcurrentModificationException, доказательство того, что он действительно работал со списком после передачи его моему коду.

person Denilson Nastacio    schedule 30.07.2011

Вы можете поместить всю свою логику в методы beforeUnmarshall, afterUnmarshall или прослушиватель Unmarshaller, и она будет выполнена после завершения разупорядочения.

См. https://stackoverflow.com/a/4378648/751200 для получения дополнительной информации.

person kolobok    schedule 14.05.2015