1

我正在使用 Java 和 Jersey 编写一个 RESTful Web 服务,该服务将接受 XML 或 JSON 输入。Jackson 用作 JSON 反序列化器,并集成到 Jersey 配置中。

其中一个端点是对 URL 的 POST 请求,其中的内容可以是几个不同的 Java 类之一,并且有一个公共基类。这些带有 XML 注释的类是:

@XmlRootElement(name = "action")
@XmlAccessorType(XmlAccessType.NONE)
@XmlSeeAlso({ FirstAction.class, SecondAction.class, ThirdAction.class })
public abstract class BaseAction {
}

@XmlRootElement(name = "first-action")
@XmlAccessorType(XmlAccessType.NONE)
public class FirstAction extends BaseAction implements Serializable {
}

// Likewise for SecondAction, ThirdAction

在我的资源中,我可以声明一个方法,例如:

@POST
@Path("/{id}/action")
public Response invokeAction(@PathParam("id") String id, BaseAction action) {...}

然后我可以发布一个看起来像的 XML 片段,<firstAction/>我的方法将被一个FirstAction实例调用。到现在为止还挺好。

我苦苦挣扎的地方是让 JSON 反序列化与 XML 反序列化一样无缝地工作。在@XmlSeeAlso注释对于使 XML 反序列化正常工作至关重要的地方,似乎 JSON 的等价物是@JsonSubTypes. 所以我这样注释类:

// XML annotations removed for brevity, but they are present as in the previous code snippet
@JsonSubTypes({ @JsonSubTypes.Type(name = "first-action", value = FirstAction.class),
    @JsonSubTypes.Type(name = "second-action", value = SecondAction.class),
    @JsonSubTypes.Type(name = "third-action", value = ThirdAction.class) })
public abstract class BaseAction {
}

@JsonRootName("first-action")
public class FirstAction extends BaseAction implements Serializable {
}

// Likewise for SecondAction, ThirdAction

然后我给它我的测试输入:{ "first-action": null }但我能得到的是:

“org.codehaus.jackson.map.JsonMappingException:根名称'first-action'与类型[简单类型,类com.alu.openstack.domain.compute.server.actions.BaseAction]的预期('action')不匹配"

不幸的是,因为我试图与其他人的 API 兼容,所以我无法更改我的示例输入 -{ "first-action": null }必须工作,并向我的方法提供 FirstAction 类的对象。(该操作没有任何字段,这就是为什么 null 不应该成为问题的原因——重要的是类的类型)。

让 JSON 反序列化以与 XML 反序列化相同的方式工作的正确方法是什么?

4

3 回答 3

2

如果您使用的是 Jackson,那么您正在寻找@JsonTypeInfo@Type. 请参阅此处了解更多信息

于 2012-08-16T11:01:35.103 回答
1

JSON 不像 XML 那样工作,因此解决方案并不相同。

您需要使用的是(就像其他答案所说)@JsonTypeInfo,. 这只会触发类型标识符的包含和使用。如果是这样,那么“@JsonSubTypes”将用于反序列化。

必须使用此指标的原因很简单:如果您有多个要反序列化的替代类型,则必须有一些区别。

还要注意,这不一定是属性——虽然大多数用户选择“As.PROPERTY”包含,但它不是(IMO)最好的方法。“WRAPPER_OBJECT”可能是您正在寻找的,因为它添加了一个额外的中间 JSON 属性,这有点类似于 XML 所做的。

于 2012-08-16T17:14:49.963 回答
0

我调查了使用@JsonTypeInfo但遇到了问题,因为我无法更改输入格式。解析器绝对必须能够处理输入{ "first-action":null }。这排除了添加@typeor@class属性的可能性。使用包装器对象可能有效,但它在null有效负载上阻塞。

一个关键点是我使用了 UNWRAP_ROOT_PROPERTY 配置选项。杰克逊绝对坚持要找到一action处房产,我无法让它考虑其他任何事情。因此,我必须有选择地为某些域对象禁用 UNWRAP_ROOT_PROPERTY,以便杰克逊对解析替代方案持开放态度。我修改了项目的 ContextResolver.getContext(...) 实现以检查@JsonRootName注释 - 因为这仅在启用包装时才有意义,所以我使用此注释的存在来确定是否返回配置了根属性包装的对象映射器,或关闭。

在这个阶段,我可能已经能够使用@JsonTypeInfo(include=JsonTypeInfo.As.WRAPPER_OBJECT, ...),除了null上面提到的有效负载问题(这用于指示子对象没有属性 - 如果我正在使用的规范给出了一个空对象 {}那么就不会有问题)。所以要继续,我需要一个自定义类型解析器。

我创建了一个扩展的新类,org.codehaus.jackson.map.TypeDeserializer目的是每当调用 Jackson 来反序列化一个BaseAction实例时,它都会调用这个自定义反序列化器。反序列化器将获得一个子类型数组,用于 BaseAction 将first-actionsecond-action等映射到 FirstAction.class 等。反序列化器读取输入流的字段名称,然后将名称与类匹配。如果下一个标记是一个对象,那么它会找到并委托给该类的适当反序列化器,或者如果它是 null,它会找到无参数构造函数并调用它来获取一个对象。

需要一个实现 org.codehaus.jackson.map.jsontype.TypeResolverBuilder 的类,它可以构建这个前一个类的实例,然后将 TypeResolverBuilder 作为类@JsonTypeResolver上的注释给出BaseAction

于 2012-08-23T09:45:38.050 回答