简体   繁体   English

使用JAXB解组时将空元素转换为null

[英]Transforming empty element into null when unmarshalling with JAXB

A class is defined with the following JAXB annotation: 使用以下JAXB注释定义类:

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

When unmarshalling an XML document that contains this 解组包含此文档的XML文档时

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

I end up with a Book added to the list, with all of its attributes set to null. 我最终将一个Book添加到列表中,其所有属性都设置为null。 I don't control the XML input. 我不控制XML输入。 How can I prevent this empty book from being added? 如何防止添加此空白书? I tried intercepting in set..() or add..() methods, but turns out JAXB bypasses setters when dealing with collections. 我尝试拦截set ..()或添加..()方法,但是在处理集合时,JAXB绕过了setter。 Any suggestions? 有什么建议?

Here's a solution that does work. 下面是工作的解决方案。 Again, elegance was left behind. 再次,优雅被遗忘。

There are two callbacks defined by JAXB: beforeUnmarshal and afterUnmarshal. JAXB定义了两个回调:beforeUnmarshal和afterUnmarshal。 By implementing afterUnmarshal to clean up the list, I was able to achieve the desired result. 通过实现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();
                }
            }
        }
    }
  }

While this works, by biggest issue with it is the absence of an interface for implementing afterUnmarshal. 虽然这是有效的,但最大的问题是没有用于实现afterUnmarshal的接口。 It's looked up by JAXB using reflection, but I think it would've been convenient (and would reduce debugging/maintenance) if JAXB simply supplied interfaces and/or absract implementations. 它是由JAXB使用反射查找的,但我认为如果JAXB只提供接口和/或抽象实现,那将会很方便(并且会减少调试/维护)。 As it is, what if the signature of afterUnmarshal changes in the future? 如果是,如果afterUnmarshal的签名将来会发生什么变化呢? This code will just mysteriously stop working as it's supposed to. 这段代码将神秘地停止工作,因为它应该。

In EclipseLink JAXB (MOXy) we have the concept of a null policy. EclipseLink JAXB(MOXy)中,我们有一个null策略的概念。 This null policy gives you some flexibility on how to represent null. 此null策略为您提供了如何表示null的灵活性。

You could modify your class to look like the following: 您可以将类修改为如下所示:

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>();
}

This informs MOXy JAXB that for this property you wish to treat empty elements as null for this property. 这通知MOXy JAXB,对于此属性,您希望将此元素的空元素视为null。

For this XML: 对于这个XML:

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

The requiredBooks property would unmarshal as: null, aBook, null. requiredBooks属性将解组为:null,aBook,null。

There is currently a bug that is preventing this from working that we are addressing now. 目前有一个错误阻止我们正在解决此问题。 You can track the progress on this issue here: 您可以在此处跟踪此问题的进度:

Well, I found one way to do it, although I don't find it super elegant. 好吧, 我找到了一种方法,虽然我发现它并不优雅。

Edit: Actually, the solution below doesn't work properly. 编辑:实际上,下面的解决方案无法正常工作。 It actually causes no entries to be added to the list, regardless of them being an empty element or fully formed with data. 实际上它不会将任何条目添加到列表中,无论它们是空元素还是完全由数据构成。

Maybe I've done it wrong, so any comments are welcome. 也许我做错了,所以欢迎任何评论。

I created an adapter to handle the conversion and informed JAXB using an annotation: 我创建了一个适配器来处理转换并使用注释通知JAXB:

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

and then defined my adapter: 然后定义我的适配器:

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;
    }
}

It works as I want it to, but, as I said earlier, I'm not convinced of its elegance. 它按照我的意愿运作,但是,正如我之前所说,我不相信它的优雅。
Edit: As described above, this doesn't work but probably due to some error on my part. 编辑:如上所述,这不起作用,但可能是由于我的一些错误。

I did a similar cludge: 我做了类似的淤泥:

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);
}

But I found it so horrible that I just ended up switching to Gson ;) 但我发现它太可怕了,我刚刚转到Gson ;)

Try this, 试试这个,

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

I had similar problem - I've need to handle an ArrayList containing interfaces. 我有类似的问题 - 我需要处理包含接口的ArrayList。 I've just created an adapter for interface (MyInterfaceAdapter) and annotated the List: 我刚刚创建了一个接口适配器(MyInterfaceAdapter)并注释了List:

@XmlElementWrapper(name="myInterface")
@XmlJavaTypeAdapter(value=MyInterfaceAdapter.class)
List<MyInterface> list = new ArryList<MyInterface>;

My adapter looks like this: 我的适配器看起来像这样:

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 ... }
    }
}

After unmarshalling I've got an initialized ArrayList (even empty). 在解组后,我有一个初始化的ArrayList(甚至是空的)。

I think in the problem asked above one can add additional conditions and return null in the case of empty book. 我认为在上面提到的问题中,可以添加其他条件,并在空书的情况下返回null。 On the other hand I also dislike returning null. 另一方面,我也不喜欢返回null。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM