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
现在,一切都已到位,我可以编写这样的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(); } }
就这样。 不要犹豫,评论解决方案。 反馈非常受欢迎,特别是在反序列化过程中,我真的不确定它对性能和并发性是否安全。