Search code examples
javajsonjacksonjava-11jackson-databind

How do I deserialize to List of Strings or Boolean from an Object using Jackson?


I was previously using jackson-databind 2.8.11.3 + Java 8 to deserialise a class ApiRequest, which was used as a request body in an API call. It has worked fine all the while and the JSON string is able to be generated.

In the Object params, either a List of Strings or boolean can be added.

Example JSON:

{
 "fields":[
  {
   "key": "colors",
   "values": ["blue","red"]
  },
  {
   "key": "metal",
   "values": true
  }
 ]
}

When I upgraded to use jackson-databind 2.13.2.2 + Java 11, and running the unit test for the generation of the JSON string, I ran to an issue where there was a bad JSON definition

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of 'com...' (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (File) .. (through reference chain: com...ApiRequest["fields"])

at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)

at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)

Below is a snippet of the class, which I have extracted and renamed:

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiRequest {
  private List<Fields> fields;

  @Data
  @Builder
  public static class Fields {
    private String key;
    private Object values;
  }
}

I have attempted to use JsonDeserializer to resolve the issue after reading up some other issues that others have posted online, but I was unsuccessful in resolving this.

Any suggestions/inputs would be greatly appreciated..

@Data
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiRequest {
  private List<Fields> fields;

  @Data
  @Builder
  @JsonDeserialize(using = FieldDeserializer.class) <-- added this
  public static class Fields {
    private String key;
    private Object values;
  }
}
public class FieldDeserializer extends JsonDeserializer<ApiRequest.Fields> {
  @Override
  public ApiRequest.Fields deserialize(JsonParser jp, DeserializationContext ctxt) 
            throws IOException, JsonProcessingException {
        
        JsonNode node = jp.getCodec().readTree(jp);
        String key = node.get("key");
        JsonNode valuesNode = node.get("values");
        Object values = null;

        if (valuesNode.isArray()) {
            List<String> listValues = new ArrayList<>();
            valuesNode.forEach(elementNode -> listValues.add(elementNode.asText()));
            values = listValues;
        } else if (valuesNode.isBoolean()) {
            values = valuesNode.asBoolean();
        }

        return new ApiRequest.Fields.builder().key(key).values(values).build();
    }
}

Solution

  • Do set @Jacksonized along with @Builder

    @Data
    @Builder
    @Jacksonized
    public class ApiRequest {
    
        private List<Field> fields;
    
        @Data
        @Builder
        @Jacksonized
        public static class Field {
    
            private String key;
            private Object values;
    
        }
    
    }