Java Jackson: deserialize complex polymorphic object model: JsonMappingException: Unexpected token (START_OBJECT), expected VALUE_STRING
Asked Answered
S

3

5

I have this tree of objects

A

B extends A

C extends B

D extends B

E extends C

F extends A and has one reference to A

A has the following annotation

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS,include=JsonTypeInfo.As.PROPERTY,property="@class")

If i try to deserialize a JSON array of objects that extends A, it throws the following error

org.codehaus.jackson.map.JsonMappingException: Unexpected token (START_OBJECT), expected VALUE_STRING: need JSON String that contains type id (for subtype of java.util.Collection)

The json string is generated by toString() method of a set and the set is parametric to type A where A is serialized in JSON with the following code:

ObjectMapper objectMapper=new ObjectMapper();
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
        String res="";
        try {
            res = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(t);
        } catch (JsonGenerationException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;

The code to deserialize the json array (that is the set described above) is:

ObjectMapper mapper = new ObjectMapper(); 

        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_CONCRETE_AND_ARRAYS);
        Collection<T> results=null;
        try {
            results =  mapper.readValue(json, TypeFactory.defaultInstance().constructParametricType(Collection.class, clazz ) );
        } catch (JsonParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (JsonMappingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
        return results;

The json sample that it parses is like:

"[{
  "@class" : "pack1.pack2.MyClass",
  "id" : null,
  "f1" : "",
  "f2" : 0.9933817827,
  "f3" : 6.883261E-4,
  "f4" : 0.001375699,
  "f5" : {
    "@class" : "pack1.pack2.MyClass2",
    "id" : null,
    "f1" : "",
    "f2" : 0.0,
    "f3" : 0.0,
    "f4" : 0.0,
    "f5" : [ "java.util.HashSet", [ 0 ] ],
    "f6" : [ "java.util.HashSet", [ 2 ] ],
    "f7" : [ "java.util.ArrayList", [ "scelta", "brani", "buona" ] ],
    "f8" : [ null, "NOM", null ],
    "f9" : false
  },
  "f10" : [ "java.util.HashMap", {
    "2" : "ADJ"
  } ],
  "f11" : [ "java.util.HashSet", [ 0 ] ],
  "f12" : [ "java.util.HashSet", [ 2 ] ],
  "f13" : [ "java.util.ArrayList", [ "scelta", "brani", "buona" ] ],
  "featureIndicator" : false
}]"

Here the json string includes only some objects of my sample of java Set

Sculpt answered 5/4, 2013 at 12:27 Comment(5)
What is the Json you are trying to parse?Hootchykootchy
I have edited the question with a sample of json stringSculpt
Probably the cause is from the missing annotations in the subclass that are subclassed too or missing annotation @JsonSubTypes in the superclass. Confirmation? I will try to correctSculpt
The annotations are "inherited", so that's not your problem. What I'm concerned about is the persence of java.util.* in your json... Ha! that's the default typing, I think. Have you tried without that?Hootchykootchy
I tried, now it throws different errors. I have added annotation to all types, but the error message is "Could not resolve type id 'F' into a subtype of [simple type, class E]"Sculpt
A
4

I believe the problem is with the default typing. The start of your JSON is not generated as what Jackson expect with default typing. The begining of the JSON should be:

["java.util.HashSet", [{

and the end should have an extra closing bracket }]].

That's because you generate the JSON using the toString() method of your set. You should instead use the ObjectMapper, that's configured with default typing, like so:

res = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(yourSet);
Acicula answered 5/4, 2013 at 15:36 Comment(0)
C
1

Maybe it will help someone. In my case, the error occurred due to the toList() method of Stream.API. This method returns an immutable collection and because of it, after serialization, the final JSON object was missing an ArrayList type. Collectors.toList() solved my problem

Before

        List<B2bItem> itemIdList = branchDto.getOrders()
            .stream()
            .map(order -> order.getItems()
                    .stream()
                    .map(OrderItemDto::getB2bItem)
                    .toList())
            .flatMap(List::stream)
            .toList(); // <---

After

        List<B2bItem> itemIdList = branchDto.getOrders()
            .stream()
            .map(order -> order.getItems()
                    .stream()
                    .map(OrderItemDto::getB2bItem)
                    .toList())
            .flatMap(List::stream)
            .collect(Collectors.toList()); // <---
Castleberry answered 17/9, 2023 at 14:10 Comment(0)
K
0

I am using @Cacheable in SpringBoot and had the same error

org.springframework.data.redis.serializer.SerializationException: Could not read JSON:Unexpected token (START_OBJECT), expected VALUE_STRING: need String, Number of Boolean value that contains type id (for subtype of java.lang.Object)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2] 

.......

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_OBJECT), expected VALUE_STRING: need String, Number of Boolean value that contains type id (for subtype of java.lang.Object)
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 2]

I would like to show more things from @Kornext's answer.

toList() returns an ImmutableList and for some reason it's serialize output will be something like

[{"@class":"...","field1":1,"field2":"2"},{"@class":"...","field1":1,"field2":"2"}]

Simply convert this to for example ArrayList and it will be serialized to

["java.util.ArrayList",[{"@class":"...","field1":1,"field2":"2"},{"@class":"...","field1":1,"field2":"2"}]]

Since "There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned" (quote from Java documentation), an explicit conversion is better:

List<B2bItem> itemIdList = branchDto.getOrders()
        .stream()
        .collect(Collectors.toCollection(ArrayList::new));
Kolo answered 14/8, 2024 at 20:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.