使用JAXB反编组时,将空元素转换为null

13

以下是使用JAXB注释定义的类:

class Course {
@XmlElement (name = "book")
List<Book> requiredBooks = new ArrayList<Book>();

当反序列化包含此内容的XML文档时

<course>
  ...
  <book/>
</course>

我最终得到了一个添加到列表中的书,其所有属性都设置为null。我无法控制XML输入。如何防止添加这本空书?我尝试在set..()或add..()方法中拦截,但结果发现JAXB在处理集合时会绕过setter。有什么建议吗?

6个回答

5

这里有一个可行的解决方案。虽然没有追求优雅的代码,但是它确实有效。

JAXB定义了两个回调函数:beforeUnmarshal和afterUnmarshal。通过实现afterUnmarshal来清理列表,我成功地实现了所需的结果。

  class Course
  {
    @XmlElement (name = "book")
    List<Book> requiredBooks = new ArrayList<Book>();  
    ...
    void afterUnmarshal(Unmarshaller aUnmarshaller, Object aParent)
    {
        if (requiredBooks != null)
        {
            Iterator<Book> iterator = requiredBooks.iterator();
            while (iterator.hasNext())
            {
                Book book = iterator.next();
                if (StringUtils.isEmpty(book.getTitle()))
                {
                    // a book without title is considered invalid
                    iterator.remove();
                }
            }
        }
    }
  }

虽然这样可以工作,但我最大的问题是缺少一个实现afterUnmarshal接口的界面。它是通过反射由JAXB查找的,但我认为如果JAXB简单地提供界面和/或抽象实现,这将非常方便(并且将减少调试/维护)。就目前而言,如果afterUnmarshal的签名在未来更改了怎么办?这段代码将会神秘地停止按照预期工作。


1
我也遇到了同样的问题。解决方案不太好,但至少它能用。你有没有找到更好的解决方法? - Matt Ball

4
EclipseLink JAXB (MOXy)中,我们有一个空值策略的概念。这个空值策略可以让你更灵活地表示空值。
你可以修改你的类,使其看起来像下面这样:
import org.eclipse.persistence.oxm.annotations.XmlMarshalNullRepresentation;
import org.eclipse.persistence.oxm.annotations.XmlNullPolicy;

@XmlRootElement
class Course {

    @XmlElement (name = "book")
    @XmlNullPolicy(
        nullRepresentationForXml = XmlMarshalNullRepresentation.EMPTY_NODE, 
        emptyNodeRepresentsNull=true)
    List<Book> requiredBooks = new ArrayList<Book>();
}

这告诉MOXy JAXB,对于此属性,您希望将空元素视为null。

对于此XML:

<course>
    <book/>
    <book>
        <title>Hello World</title>
    </book>
    <book/>
</course>

requiredBooks 属性将被解组为:null、aBook、null。

目前存在一个错误,阻止了此功能的正常工作,我们正在解决这个问题。您可以在此处跟踪此问题的进展:


是的,我知道 MOXy 有这种可能性(另一个线程讨论过)。我只能希望它在 JAXB 中默认可用。似乎很奇怪为什么会忽略这样的选项。 - torngat

2

嗯,我发现一种方法可以实现它,虽然我不认为它非常优雅。

编辑:实际上,下面的解决方案不能正常工作。无论元素是空元素还是含有数据,都不会将任何条目添加到列表中。

也许我做错了什么,所以欢迎任何评论。

我创建了一个适配器来处理转换,并使用注释通知JAXB:

class Course {
@XmlElement (name = "book")
@XmlJavaTypeAdapter(BookListAdapter.class)
List<Book> requiredBooks = new ArrayList<Book>();

然后我定义了我的适配器:

static class BookListAdapter extends XmlAdapter<Book[], List<Book>>
{
    public Book[] marshal(List<Book> aBookList)
    {
        if (aBookList!= null)
        {
            return aBookList.toArray(new Book[aBookList.size()]);
        }
        return null;
    }

    public List<Book> unmarshal(Book[] aBookArray)
    {
        List<Book> bookList = new ArrayList<Book>();
        if (aBookArray != null)
        {
            for (Book book : aBookArray)
            {
                if (StringUtils.isNotEmpty(book.getTitle()))
                {
                    bookList.add(book);
                }
            }
        }
        return bookList;
    }
}

它按照我想要的方式工作,但是正如我之前所说,我对其优雅性并不满意。
编辑:如上所述,这并不起作用,但可能是由于我的错误导致的。


1

试试这个,

class Course {
@XmlElement (name="book", required=false)
List<Book> requiredBooks = new ArrayList<Book>();

嗨。谢谢,但它没有起作用。我想这个指令告诉JAXB如果元素完全缺失就不要将其视为错误,但当出现空标签的情况时,它并不会改变行为。 - torngat
@torngat:你尝试过在nillable中使用required吗?实际上,我只是瞎猜,因为我无法相信它没有任何内置解决方案来满足这个非常基本的合理要求。 - Adeel Ansari
是的,我已经尝试过了,但没有结果。 我目前的理解是,这些额外的指令主要用于编组(即生成)XML。 然后,编组器将知道某个元素不是必需的,但如果它为空,它将被包含在内,如下所示:<element xsd:nil="true"/>。 - torngat
@torngat:如果是这样的话,为什么不尽可能地避免使用JAXB,而改用XStream或Simple XML Framework呢?它们都具备此功能。我相信你不会错过任何东西。或者也许可以探索一下XMLBeans,它与JAXB非常相似,也具备此功能。 - Adeel Ansari

1

我做了一个类似的临时解决方案:

public List<Photo> getPhotos() {
    removeUninitializedPhotos();
    return photos;
}

private void removeUninitializedPhotos() {
    List<Photo> photosToRemove = new ArrayList<Photo>();
    for (Photo photo : photos) {
        if (photo.getId() == null) {
            photosToRemove.add(photo);
        }
    }
    photos.removeAll(photosToRemove);
}

但我觉得那太可怕了,最终就转而使用 Gson 了;)


0
我遇到了类似的问题 - 我需要处理一个包含接口的ArrayList。我只是为接口创建了一个适配器(MyInterfaceAdapter),并对列表进行了注释:
@XmlElementWrapper(name="myInterface")
@XmlJavaTypeAdapter(value=MyInterfaceAdapter.class)
List<MyInterface> list = new ArryList<MyInterface>;

我的适配器长这样:

public class MyInterfaceAdapter extends XmlAdapter<MyType, MyInterface> {

@Override
public Sensor unmarshal(MyType v) throws Exception {
    return (MyInterface) v;
}

@Override
public AbstractSensor marshal(MyInterface v) throws Exception {     
        if(v == null) //In the case of empty list
        {
         return null;
        } else
        { .....return new ... }
    }
}

反序列化后,我得到了一个已初始化的ArrayList(即使是空的)。

我认为在上述问题中,可以添加其他条件,并在书籍为空的情况下返回null。另一方面,我也不喜欢返回null。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接