【问题标题】:Parsing JSON Array with numbers as keys using Jackson?使用杰克逊解析带有数字作为键的 JSON 数组?
【发布时间】:2015-10-24 09:26:35
【问题描述】:

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

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

【问题讨论】:

  • 您想保留数组中的索引还是只保留排序?即您希望result[0] 是第一个条目,还是result[1]
  • @araqnid 我想使用 Jackson 来按顺序解析数据

标签: java android json parsing jackson


【解决方案1】:

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

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

但是,如果您只想在模型中使用 Collection&lt;Value&gt;,并隐藏此细节,您可以创建一个自定义反序列化器来执行此操作。请注意,您需要为反序列化器实现“上下文化”:它需要了解集合中对象的类型。 (虽然我猜如果你只有一个案例,你可以硬编码它,但它的乐趣在哪里?)

@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()equals(x)等的定义为了可读性都省略了)

最后是反序列化器的实现:

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 的源代码。

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

最后,如果您希望反序列化器返回保留索引的 List,您可以修改上面的内容,只需插入一点 TreeMap 的后处理:

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

【讨论】:

  • 我无法理解你的答案...你能不能让它更容易一些,可能你的回答是让我的事情变得更复杂......
  • 可能只使用答案中的第一个建议:这会将输入字符串解析为SortedMap&lt;Integer,Value&gt;,然后您可以调用values() 按顺序获取值,而无需处理使用解串器等。它可以缩短为SortedMap&lt;Integer,Value&gt; map = mapper.readValue(input, new TypeReference&lt;SortedMap&lt;Integer,Value&gt;&gt;(){})
  • 对于 line .reader(new TypeReference>() {}) .with(JsonParser.Feature.ALLOW_SINGLE_QUOTES).....我得到以下错误方法 with( ObjectReader 类型中的 DeserializationConfig) 不适用于参数 (JsonParser.Feature)
  • @shriduttkothari:您使用的是哪个版本的 Jackson?我正在使用 2.5 版本对此进行测试。您不需要启用这些解析器功能,它们只是让编写测试数据变得更好。
  • 我使用的是 2.2.3 版
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-25
  • 2019-07-27
  • 2013-07-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多