Parsing JSON Array with numbers as keys using Jackson?

前端 未结 1 785
暖寄归人
暖寄归人 2021-01-16 11:36

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

{
  \"1\": {
    \"title\": \"ABC\",
    \"category\": \"Video\         


        
相关标签:
1条回答
  • 2021-01-16 11:50

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

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

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

    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 = new Object[capacity];
            values.forEach((key, value) -> objects[key] = value);
            return Arrays.asList(objects);
    
    0 讨论(0)
提交回复
热议问题