skip to Main Content

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

2

Answers


  1. 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;
    
        }
    
    }
    
    Login or Signup to reply.
  2. Refer to Oleg’s answer if you are fine with experimental feature.
    @Jacksonized is perfect match for this scenario.

    Another way to solve this issue using stable API is by adding @NoArgsConstructor and @AllArgsConstructor to ApiRequest and Field.

    • As a drawback, this will expose the constructors to others, where @Jacksonized does not.
      This could also be rectified by adding access = AccessLevel.PRIVATE to the annotation (suppose jackson has access to your Module).
    @Data
    @Builder
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @NoArgsConstructor
    @AllArgsConstructor
    public class ApiRequest {
      private List<Fields> fields;
    
      @Data
      @Builder
      @NoArgsConstructor
      @AllArgsConstructor
      public static class Fields {
        private String key;
        private Object values;
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search