使用Jackson解析带有数字作为键的JSON数组? [英] Parsing JSON Array with numbers as keys using Jackson?

查看:1023
本文介绍了使用Jackson解析带有数字作为键的JSON数组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何使用Jackson解析以下类型的JSON数组并保留内容的顺序:

How to parse following kind of JSON Array using Jackson with preserving order of the content:

{
  "1": {
    "title": "ABC",
    "category": "Video",
  },
  "2": {
    "title": "DEF",
    "category": "Audio",
  },
  "3": {
    "title": "XYZ",
    "category": "Text",
  }
}


推荐答案

一个简单的解决方案:而不是直接将其反序列化为数组/列表,将其反序列化为 SortedMap< Integer,Value> ,然后只需调用 values()就可以按顺序获取值。有点混乱,因为它暴露了模型对象中JSON处理的细节,但这是最不实现的工作。

One simple solution: rather than deserializing it directly as an array/list, deserialize it to a SortedMap<Integer, Value> and then just call values() on that to get the values in order. A bit messy, since it exposes details of the JSON handling in your model object, but this is the least work to implement.

@Test
public void deserialize_object_keyed_on_numbers_as_sorted_map() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    SortedMap<Integer, Value> container = mapper
            .reader(new TypeReference<SortedMap<Integer, Value>>() {})
            .with(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
            .with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
            .readValue(
                    "{ 1: { title: 'ABC', category: 'Video' }, 2: { title: 'DEF', category: 'Video' }, 3: { title: 'XYZ', category: 'Video' } }");
    assertThat(container.values(),
            contains(new Value("ABC", "Video"), new Value("DEF", "Video"), new Value("XYZ", "Video")));
}


public static final class Value {
    public final String title;
    public final String category;

    @JsonCreator
    public Value(@JsonProperty("title") String title, @JsonProperty("category") String category) {
        this.title = title;
        this.category = category;
    }
}

但是如果你想要一个<$ c您的模型中的$ c> Collection< Value> ,并隐藏此详细信息,您可以创建自定义反序列化器来执行此操作。请注意,您需要为反序列化器实现上下文化:它需要知道集合中对象的类型。 (虽然你可以硬编码,如果你只有一个案例,我想,但那里的乐趣在哪里?)

But if you want to just have a Collection<Value> in your model, and hide this detail away, you can create a custom deserializer to do that. Note that you need to implement "contextualisation" for the deserializer: it will need to be aware of what the type of the objects in your collection are. (Although you could hardcode this if you only have one case of it, I guess, but where's the fun in that?)

@Test
public void deserialize_object_keyed_on_numbers_as_ordered_collection() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    CollectionContainer container = mapper
            .reader(CollectionContainer.class)
            .with(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
            .with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
            .readValue(
                    "{ values: { 1: { title: 'ABC', category: 'Video' }, 2: { title: 'DEF', category: 'Video' }, 3: { title: 'XYZ', category: 'Video' } } }");
    assertThat(
            container,
            equalTo(new CollectionContainer(ImmutableList.of(new Value("ABC", "Video"), new Value("DEF", "Video"),
                    new Value("XYZ", "Video")))));
}


public static final class CollectionContainer {
    @JsonDeserialize(using = CustomCollectionDeserializer.class)
    public final Collection<Value> values;

    @JsonCreator
    public CollectionContainer(@JsonProperty("values") Collection<Value> values) {
        this.values = ImmutableList.copyOf(values);
    }
}

(注意 hashCode的定义()等于(x)等因为可读性而被省略。)

(note definitions of hashCode(), equals(x) etc. are all omitted for readability)

最后是反序列化器实现:

And finally here comes the deserializer implementation:

public static final class CustomCollectionDeserializer extends StdDeserializer<Collection<?>> implements
        ContextualDeserializer {
    private JsonDeserializer<Object> contentDeser;

    public CustomCollectionDeserializer() {
        super(Collection.class);
    }

    public CustomCollectionDeserializer(JavaType collectionType, JsonDeserializer<Object> contentDeser) {
        super(collectionType);
        this.contentDeser = contentDeser;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        if (!property.getType().isCollectionLikeType()) throw ctxt
                .mappingException("Can only be contextualised for collection-like types (was: "
                        + property.getType() + ")");
        JavaType contentType = property.getType().getContentType();
        return new CustomCollectionDeserializer(property.getType(), ctxt.findContextualValueDeserializer(
                contentType, property));
    }

    @Override
    public Collection<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
            JsonProcessingException {
        if (contentDeser == null) throw ctxt.mappingException("Need context to produce elements of collection");
        SortedMap<Integer, Object> values = new TreeMap<>();
        for (JsonToken t = p.nextToken(); t != JsonToken.END_OBJECT; t = p.nextToken()) {
            if (t != JsonToken.FIELD_NAME) throw ctxt.wrongTokenException(p, JsonToken.FIELD_NAME,
                    "Expected index field");
            Integer index = Integer.valueOf(p.getText());
            p.nextToken();
            Object value = contentDeser.deserialize(p, ctxt);
            values.put(index, value);
        }
        return values.values();
    }
}

这至少涵盖了这个简单的案例:像作为多态类型的集合的内容可能需要更多处理:请参阅Jackson自己的CollectionDeserializer的源代码。

This covers at least this simple case: things like the contents of the collection being polymorphic types may require more handling: see the source of Jackson's own CollectionDeserializer.

此外,如果没有上下文,您可以使用UntypedObjectDeserializer作为默认值而不是choking给出。

Also, you could use UntypedObjectDeserializer as a default instead of choking if no context is given.

最后,如果你想让反序列化器返回一个保留了索引的List,你可以修改上面的内容并插入一些后处理TreeMap:

Finally, if you want the deserializer to return a List with the indices preserved, you can modify the above and just insert a bit of post-processing of the TreeMap:

        int capacity = values.lastKey() + 1;
        Object[] objects = new Object[capacity];
        values.forEach((key, value) -> objects[key] = value);
        return Arrays.asList(objects);

这篇关于使用Jackson解析带有数字作为键的JSON数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆