有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

JavaJersey、Jackson和JAXRS发布了多种JSON格式

我试图定义以下代码:

public class MyObject {
    private String name;
    ... // Other attributes
}

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

我知道我需要使用:

DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true

要正确设置对象映射器,以便能够在rest资源上接受单个值作为数组。我成功地安装了那个部件

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

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

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

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

那么,我的问题是如何做到这一点。我们的想法是只有一个@POST同时接受数组和单个值

理想情况下,我将放弃ObjectMapper的配置,以避免将单个对象设置到JSON文档的其他级别。例如,我不想允许:

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

通常情况下,这种格式是强制性的:

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

基于此,我尝试在列表中放置一个对象包装器,以设置是否能够识别列表和对象之间的差异。比如:

public class ObjectWrapper<T> {
    private List<T> 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<T> extends JsonDeserializer<T> {
    @Override
    public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ... // What to put there to deserialize Array or Object
    }
}

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

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

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

非常感谢


共 (1) 个答案

  1. # 1 楼答案

    好的,我终于成功地建立了一个机制,它完全符合我的要求。但是,我不确定这样的表现或诸如此类的事情是否会产生负面影响

    首先,我定义了一个可以同时接受列表或单个对象的类:

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

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

    public class RootWrapperDeserializer extends JsonDeserializer<CollectionWrapper<?>> {
        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<JsonNode> 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负责下一个操作。我只需要知道我希望包装中会出现哪一种真正的类型

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

    public class CustomDeserializers extends Deserializers.Base {
        // Deserializers caching
        private Map<Class, RootWrapperDeserializer> 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的提供程序中,我可以将反序列化器注册表设置为创建的对象映射器,如下所示:

    @Provider
    @Produces(MediaType.APPLICATION_JSON)
    public class JsonObjectMapper implements ContextResolver<ObjectMapper> {
        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;
        }
    }
    

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

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

    现在,一切就绪,我可以编写一个REST资源方法,如下所示:

    @POST
    @Path("somePath")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(RootWrapper<SpecificClass> 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();
        }
    }
    

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