Parsing Json Array With Numbers As Keys Using Jackson?
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?"