如何使用 JAXB XmlAdapter 来编组列表?

Dis*_*tum 2 java json jaxb jersey xmladapter

此类的实例是大型对象图的一部分,并且不在对象图的根部:

public class Day
{
    public Day(LocalDate date, List<LocalTime> times)
    {
        this.date = date;
        this.times = times;
    }

    public Day()
    {
        this(null, null);
    }

    public LocalDate getDate()
    {
        return date;
    }

    public List<LocalTime> getTimes()
    {
        return times;
    }

    private final LocalDate date;

    private final List<LocalTime> times;
}
Run Code Online (Sandbox Code Playgroud)

使用 Jersey 和 JAXB 将对象图转换为 JSON。我已经XmlAdapter注册了LocalDateLocalTime

问题是它只对date财产有效,而不是对times财产有效。我怀疑这与列表times而不是单个值有关。那么,我如何告诉 Jersey/JAXBtimes使用注册的来编组列表中的每个元素XmlAdapter

更新:

通过添加标量属性并观察 JSON 中的预期输出,我确认LocalTime编组确实适用于标量属性。LocalTimeLocalTime

为了完整起见,这里是 package-info.java:

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(value = LocalDateAdapter.class, type = LocalDate.class),
    @XmlJavaTypeAdapter(value = LocalTimeAdapter.class, type = LocalTime.class)
})
package same.package.as.everything.else;
Run Code Online (Sandbox Code Playgroud)

LocalDateAdapter:

public class LocalDateAdapter extends XmlAdapter<String, LocalDate>
{
    @Override
    public LocalDate unmarshal(String v) throws Exception
    {
        return formatter.parseLocalDate(v);
    }

    @Override
    public String marshal(LocalDate v) throws Exception
    {
        return formatter.print(v);
    }

    private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMdd");
}
Run Code Online (Sandbox Code Playgroud)

LocalTimeAdapter:

public class LocalTimeAdapter extends XmlAdapter<String, LocalTime>
{
    @Override
    public LocalTime unmarshal(String v) throws Exception
    {
        return formatter.parseLocalTime(v);
    }

    @Override
    public String marshal(LocalTime v) throws Exception
    {
        return formatter.print(v);
    }

    private final DateTimeFormatter formatter = DateTimeFormat.forPattern("HHmm");
Run Code Online (Sandbox Code Playgroud)

bdo*_*han 5

类的forXmlAdapter应用于该类型的映射字段/属性,对于集合,应用于集合中的每个项目。下面的例子证明这是可行的。您是否尝试过独立运行 XML 示例来验证映射。怀疑问题是除XmlAdapter具体问题之外的其他问题。

字符串适配器

以下XmlAdapter将在解组操作中将 a 转换String为小写,并在编组时将其转换为大写。

package forum14569293;

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

public class StringAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(String v) throws Exception {
        return v.toLowerCase();
    }

    @Override
    public String marshal(String v) throws Exception {
        return v.toUpperCase();
    }

}
Run Code Online (Sandbox Code Playgroud)

包信息

正如您的问题一样,@XmlJavaTypeAdapters将使用包级别注释来注册XmlAdapter. 这将为该包中的XmlAdapter所有映射属性注册此属性(请参阅:http: //blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html)。String

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum14569293;

import javax.xml.bind.annotation.adapters.*;
Run Code Online (Sandbox Code Playgroud)

下面是一个示例域模型,与您的Day类类似,具有两个映射属性。第一个是 type String,第二个是List<String>。我注意到你的班级的一件事Day是你只有get方法。这意味着您需要@XmlElement为 JAXB impl 添加注释才能将其视为映射属性。

package forum14569293;

import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class Root {

    public Root(String foo, List<String> bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public Root() {
        this(null, null);
    }

    @XmlElement
    public String getFoo() {
        return foo;
    }

    @XmlElement
    public List<String> getBar() {
        return bar;
    }

    private final String foo;
    private final List<String> bar;

}
Run Code Online (Sandbox Code Playgroud)

演示

package forum14569293;

import java.util.*;
import javax.xml.bind.*;

public class Demo {

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

        List<String> bar = new ArrayList<String>(3);
        bar.add("a");
        bar.add("b");
        bar.add("c");
        Root root = new Root("Hello World", bar);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);

    }

}
Run Code Online (Sandbox Code Playgroud)

输出

下面是运行演示代码的输出,我们看到所有字符串都被XmlAdapter.

package forum14569293;

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

public class StringAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(String v) throws Exception {
        return v.toLowerCase();
    }

    @Override
    public String marshal(String v) throws Exception {
        return v.toUpperCase();
    }

}
Run Code Online (Sandbox Code Playgroud)

更新

谢谢。我尝试了一下,XML 仅包含一个空标签,这意味着 JAXB 不喜欢 POJO 模型中的某些内容。(也许它应该是可序列化的?)

JAXB 不要求 POJO 实现Serializable.

这很有趣,因为它似乎表明 JAXB 在这方面发挥的唯一作用是将其注释和一些其他接口(例如 XmlAdapter)借给 JSON(反)序列化器,这就是关系结束的地方。

这取决于用作 JSON 绑定层的内容。JAXB (JSR-222)规范不涵盖 JSON 绑定,因此这种类型的支持超出了规范。EclipseLink JAXB (MOXy) 提供本机 JSON 绑定(我是 MOXy 的负责人),您可以使用它执行以下操作:

Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
marshaller.marshal(root, System.out);
Run Code Online (Sandbox Code Playgroud)

并得到以下考虑XmlAdapter在内的输出:

{
   "bar" : [ "A", "B", "C" ],
   "foo" : "HELLO WORLD"
}
Run Code Online (Sandbox Code Playgroud)

尽管如此,当我有机会时,我会尽我所能让 JAXB 生成 XML,这可能会揭示其他内容。

这将很有用,因为我不相信您看到的是 JAXB 问题,而是您正在使用的 JSON 绑定层中的问题。