Skip to content Skip to sidebar Skip to footer

Parsing Json Array With Numbers As Keys Using Jackson?

How to parse following kind of JSON Array using Jackson with preserving order of the content: { '1': { 'title': 'ABC', 'category': 'Video', }, '2': { 'title': 'DE

Solution 1:

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.

@Testpublicvoiddeserialize_object_keyed_on_numbers_as_sorted_map()throws Exception {
    ObjectMappermapper=newObjectMapper();
    SortedMap<Integer, Value> container = mapper
            .reader(newTypeReference<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(newValue("ABC", "Video"), newValue("DEF", "Video"), newValue("XYZ", "Video")));
}


publicstaticfinalclassValue {
    publicfinal String title;
    publicfinal String category;

    @JsonCreatorpublicValue(@JsonProperty("title") String title, @JsonProperty("category") String category) {
        this.title = title;
        this.category = category;
    }
}

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?)

@Testpublicvoiddeserialize_object_keyed_on_numbers_as_ordered_collection()throws Exception {
    ObjectMappermapper=newObjectMapper();
    CollectionContainercontainer= 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(newCollectionContainer(ImmutableList.of(newValue("ABC", "Video"), newValue("DEF", "Video"),
                    newValue("XYZ", "Video")))));
}


publicstaticfinalclassCollectionContainer {
    @JsonDeserialize(using = CustomCollectionDeserializer.class)publicfinal Collection<Value> values;

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

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

And finally here comes the deserializer implementation:

publicstaticfinalclassCustomCollectionDeserializerextendsStdDeserializer<Collection<?>> implementsContextualDeserializer {
    private JsonDeserializer<Object> contentDeser;

    publicCustomCollectionDeserializer() {
        super(Collection.class);
    }

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

    @Overridepublic 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() + ")");
        JavaTypecontentType= property.getType().getContentType();
        returnnewCustomCollectionDeserializer(property.getType(), ctxt.findContextualValueDeserializer(
                contentType, property));
    }

    @Overridepublic 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 = newTreeMap<>();
        for (JsonTokent= p.nextToken(); t != JsonToken.END_OBJECT; t = p.nextToken()) {
            if (t != JsonToken.FIELD_NAME) throw ctxt.wrongTokenException(p, JsonToken.FIELD_NAME,
                    "Expected index field");
            Integerindex= Integer.valueOf(p.getText());
            p.nextToken();
            Objectvalue= contentDeser.deserialize(p, ctxt);
            values.put(index, value);
        }
        return values.values();
    }
}

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.

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

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 = newObject[capacity];
        values.forEach((key, value) -> objects[key] = value);
        return Arrays.asList(objects);

Post a Comment for "Parsing Json Array With Numbers As Keys Using Jackson?"