0

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

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

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

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

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

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

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

然后,我的问题是如何做这样的事情。这个想法是只有一个@POST 可以同时接受数组和单个值?

理想情况下,我将摆脱 ObjectMapper 的配置,以避免将 Single Object 设置到 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 注释的类中配置的特性。

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

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

提前非常感谢。

4

1 回答 1

0

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

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

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;
    }
}

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

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

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;
    }
}

好的,然后我有我的反序列化器注册表,它只根据需要创建新的反序列化器,并在创建后保留它们。我不确定这种方法是否存在任何并发问题。我知道杰克逊做了很多缓存,并且一旦 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();
    }
}

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

于 2013-11-28T10:24:51.433 回答