Jersey,Jackson和JAX-RS POST多种JSON格式

我试图能够定义以下代码:

public class MyObject { private String name; ... // Other attributes } @Path(...) @Stateless public class MyRestResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response create(List myObjects) { // Do some stuff there } } 

我知道我需要使用:

 DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true 

正确设置我的对象映射器,以便能够在我的其余资源上接受单个值作为数组。 我成功地设置了那个部分。

我对这种方法的问题是以下内容不可区分:

 { "name": "a name", ... // other attributes } 

 [{ "name": "a name", ... // other attributes }] 

将导致大小为一的列表(List)。 然后,在方法create(List myObjects)中,我将无法区分List和发送到Rest资源的单个对象。

然后,我的问题是如何做这样的事情。 我们的想法是只有一个接受Arrays和Single值的@POST?

理想情况下,我将摆脱ObjectMapper的配置,以避免将单个对象设置为JSON文档的其他级别的可能性。 例如,我不想允许:

 { ... "attributes": { ... } } 

通常这种格式应该是强制性的:

 { ... "attributes": [{ ... }] } 

基于此,我试图设置我的List的对象包装器来设置我是否能够区分列表和对象。 有这样的事情:

 public class ObjectWrapper { private List list; private T object; public boolean isObject() { return list == null; } } 

随着资源变成:

 @Path(...) @Stateless public class MyRestResource { @POST @Consumes(MediaType.APPLICATION_JSON) public Response create(ObjectWrapper myObjects) { // Do some stuff there } } 

并尝试通过JAX-RS / Jersey / Jackson机制对我的内容进行反序列化。 如果我现在使用解决方案,则反序列化会因为预期的JSON格式如下而失败:

 { "list": [{ "name": "a name", ... // other attributes }] } 

然后我尝试编写一个自定义反序列化器,但我在这个任务中有点迷失。 我有类似的东西:

 public class ObjectWrapperDeserializer extends JsonDeserializer { @Override public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ... // What to put there to deserialize Array or Object } } 

我只想反序列化根级别,将反序列化的内容设置为对象包装器。 我还希望在完成不同@Provider的配置时,在使用@ApplicationPath注释的类中配置该function。

我希望所有信息都能充分说明我想做什么以及我已经测试过的内容。

等待有关如何在同一路径上执行接受数组或对象的资源的建议。

非常感谢提前。

好的,最后我成功地建立了一个完全符合我要求的机制。 但是,我不确定是否存在诸如表现或此类事情的负面后果。

首先,我定义了一个可以接受List或Single Object的类:

 public class RootWrapper { private List list; private T object; } 

然后,我需要一个自定义反序列化器,它能够知道要反序列化哪种类型的T类型以及处理集合或单个对象。

 public class RootWrapperDeserializer extends JsonDeserializer> { private Class contentType; public RootWrapperDeserializer(Class contentType) { this.contentType = contentType; } @Override public RootWrapper deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { // Retrieve the object mapper and read the tree. ObjectMapper mapper = (ObjectMapper) jp.getCodec(); JsonNode root = mapper.readTree(jp); RootWrapper wrapper = new RootWrapper(); // Check if the root received is an array. if (root.isArray()) { List list = new LinkedList(); // Deserialize each node of the array using the type expected. Iterator rootIterator = root.getElements(); while (rootIterator.hasNext()) { list.add(mapper.readValue(rootIterator.next(), contentType)); } wrapper.setList(list); } // Deserialize the single object. else { wrapper.setObject(mapper.readValue(root, contentType)); } return wrapper; } } 

据我所知,我尝试只手动反序列化根级别,然后让Jackson接下来的操作。 我只需要知道我希望在Wrapper中出现哪种真实类型。

在这个阶段,我需要一种方法告诉Jersey / Jackson使用哪个解串器。 我找到的一种方法是创建一种反序列化器注册表,其中存储了使用正确的反序列化器反序列化的类型。 我扩展了Deserializers.Base类。

 public class CustomDeserializers extends Deserializers.Base { // Deserializers caching private Map deserializers = new HashMap<>(); @Override public JsonDeserializer findBeanDeserializer(JavaType type, DeserializationConfig config, DeserializerProvider provider, BeanDescription beanDesc, BeanProperty property) throws JsonMappingException { // Check if we have to provide a deserializer if (type.getRawClass() == RootWrapper.class) { // Check the deserializer cache if (deserializers.containsKey(type.getRawClass())) { return deserializers.get(type.getRawClass()); } else { // Create the new deserializer and cache it. RootWrapperDeserializer deserializer = new RootWrapperDeserializer(type.containedType(0).getRawClass()); deserializers.put(type.getRawClass(), deserializer); return deserializer; } } return null; } } 

好的,然后我有我的反序列化器注册表,只在需要时创建新的反序列化器并保留它们一旦创建。 我不确定这种方法是否存在任何并发问题。 我知道Jackson做了很多缓存,并且一旦在特定的反序列化上下文中第一次调用findBeanDeserializer,就不会调用它。

现在我创建了不同的类,我需要做一些管道将所有内容组合在一起。 在我创建ObjectMapper的提供程序中,我可以将deserializers注册表设置为创建的对象映射器,如下所示:

 @Provider @Produces(MediaType.APPLICATION_JSON) public class JsonObjectMapper implements ContextResolver { private ObjectMapper jacksonObjectMapper; public JsonObjectMapper() { jacksonObjectMapper = new ObjectMapper(); // Do some custom configuration... // Configure a new deserializer registry jacksonObjectMapper.setDeserializerProvider( jacksonObjectMapper.getDeserializerProvider().withAdditionalDeserializers( new RootArrayObjectDeserializers() ) ); } @Override public ObjectMapper getContext(Class arg0) { return jacksonObjectMapper; } } 

然后,我还可以定义我的REST应用程序的@ApplicationPath,如下所示:

 public abstract class AbstractRestApplication extends Application { private Set> classes = new HashSet<>(); public AbstractRestApplication() { classes.add(JacksonFeature.class); classes.add(JsonObjectMapper.class); addResources(classes); } @Override public Set> getClasses() { return classes; } @Override public Set getSingletons() { final Set singletons = new HashSet<>(1); singletons.add(new JacksonJsonProvider()); return singletons; } private void addResources(Set> classes) { classes.add(SomeRestResource.class); // ... } } 

现在,一切都已到位,我可以编写这样的REST资源方法:

 @POST @Path("somePath") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response create(RootWrapper wrapper) { if (wrapper.isObject()) { // Do something for one single object SpecificClass sc = wrapper.getObject(); // ... return Response.ok(resultSingleObject).build(); } else { // Do something for list of objects for (SpecificClass sc = wrapper.getList()) { // ... } return Response.ok(resultList).build(); } } 

就这样。 不要犹豫,评论解决方案。 反馈非常受欢迎,特别是在反序列化过程中,我真的不确定它对性能和并发性是否安全。