Skip to content
Advertisement

@JsonProperty not working after springboot upgrade

I recently upgraded my application from Spring Boot 1.5.3 to Spring Boot 2.3.5. One of the feature on UI started breaking and I found while debugging that the json response to UI had changed

Original response:

"modelExplanation": [{
                                "sectionId": 0,
                                "sectionText": "some text",
                                "startIndex": "0",
                                "endIndex": "105",
                                "modelExplanations": [{
                                                "modelId": "mdl_collusion",
                                                "modelName": "collusion",
                                                "modelVersion": "3.4.1",
                                                "score": "1.0",
                                                "riskStatus": "low"
                                },
                                {
                                                "modelId": "mdl_mkt_mnpltn",
                                                "modelName": "market_manipulation",
                                                "modelVersion": "3.4.1",
                                                "score": "1.0",
                                                "riskStatus": "low"
                                },
                                {
                                                "modelId": "mdl_secrecy",
                                                "modelName": "secrecy",
                                                "modelVersion": "3.4.1",
                                                "score": "1.0",
                                                "riskStatus": "low"
                                }]
                }]
}

New response: having underscores (_) in attribute names

                                "section_id": 1,
                                "section_text": "some text",
                                "start_index": "738",
                                "end_index": "1112",
                                "model_explanations": [{
                                                "model_id": "mdl_collusion",
                                                "model_name": "collusion",
                                                "model_version": "3.4.1",
                                                "section_score": "0.09059832",
                                                "risk_status": "low"
                                },
                                {
                                                "model_id": "mdl_mkt_mnpltn",
                                                "model_name": "market_manipulation",
                                                "model_version": "3.4.1",
                                                "section_score": "0.12787165",
                                                "risk_status": "low"
                                },
                                {
                                                "model_id": "mdl_secrecy",
                                                "model_name": "secrecy",
                                                "model_version": "3.4.1",
                                                "section_score": "0.19208406",
                                                "risk_status": "low"
                                }]
                }]
}

It seems that the @JsonProperty is not working. My classes are as below:

  public ModelExplanationResponse getModelExplanation(User user, ModelRequest request) {
    ModelExplanationMetadata metadata = null;
    try {
        String modelExplanationJson = anotherService.getModelExplanation(user, request.getMessageId(), request.getSource(), request.getAvroId(), request.getRundate());
        
        if (modelExplanationJson != null && !"".equals(modelExplanationJson)) {
            metadata = mapJSONToMetadata(modelExplanationJson);
        }
    } catch (Exception e) {
        throw new AlertMngtServiceException("Unable to retrieve attachment",e);
    }
    return getModelExplanationResponse(metadata, request);
}

private ModelExplanationResponse getModelExplanationResponse(ModelExplanationMetadata metadata, ModelRequest request) {
    ModelExplanationResponse response = new ModelExplanationResponse();
    if (metadata != null && metadata.getModelExplanation() != null) {
        List<ModelExplanationSection> modelExplanation = mapJSONToModelExplanation(metadata.getModelExplanation());
        if (!CollectionUtils.isEmpty(modelExplanation)) {
            modelExplanation.sort(Comparator.comparing(ModelExplanationSection::getSectionId));
            response.setModelExplanation(modelExplanation);
            String formattedMessageBody = new ModelExplanationBasedMessageModifier().modify(request.getMessageBody(), modelExplanation);               
            response.setMessageBody(formattedMessageBody);
        }
    } else {
        LOGGER.info("Unable to fetch Model Explanation for {}", request);
    }
    return response;
}


import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ModelExplanationSection {

    private final int sectionId;
    private final String sectionText;
    private final String startIndex;
    private final String endIndex;
    private final List<ModelExplanation> modelExplanations;

    @JsonCreator
    public ModelExplanationSection(
            @JsonProperty("section_id") int sectionId,
            @JsonProperty("section_text") String sectionText,
            @JsonProperty("start_index") String startIndex,
            @JsonProperty("end_index") String endIndex,
            @JsonProperty("model_explanations") List<ModelExplanation> modelExplanations) {
        super();
        this.sectionId = sectionId;
        this.sectionText = sectionText;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
        this.modelExplanations = modelExplanations;
    }

    public int getSectionId() {
        return sectionId;
    }

    public String getSectionText() {
        return sectionText;
    }

    public String getStartIndex() {
        return startIndex;
    }

    public String getEndIndex() {
        return endIndex;
    }

    public List<ModelExplanation> getModelExplanations() {
        return modelExplanations;
    }

}




import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Strings;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ModelExplanation {

    private final String modelId;
    private final String modelName;
    private final String modelVersion;
    private final String score;
    private final String riskStatus;

    @JsonCreator
    public ModelExplanation(
            @JsonProperty("model_id") String modelId,
            @JsonProperty("model_name") String modelName,
            @JsonProperty("model_version") String modelVersion,
            @JsonProperty("section_score") String score,
            @JsonProperty("risk_status") String riskStatus) {
        super();
        this.modelId = modelId;
        this.modelName = modelName;
        this.modelVersion = modelVersion;
        this.score = score;
        this.riskStatus = getRiskStatus(riskStatus);
    }

    private String getRiskStatus(String riskStatus) {
        return Strings.isNullOrEmpty(riskStatus)?"low":riskStatus;
    }

    public String getModelId() {
        return modelId;
    }

    public String getModelName() {
        return modelName;
    }

    public String getModelVersion() {
        return modelVersion;
    }

    public String getScore() {
        return score;
    }

    public String getRiskStatus() {
        return riskStatus;
    }

}

I tried @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) but it doesn’t work. Found another solution on stack-overflow where it was suggested to replace the jackson annotation import to org.codehaus.jackson.annotate but this didn’t work for me either.

The same classes are being used for 2 purpose here, 1. @JsonProperty is being used to map json response from anotherService to java object and then the same java object is being used to serialise the jave object to ui. I cannot understand how was it working before the ugrade.

Any help is greatly apreciaed as this has already taken one day but I couldn’t identify and resolve the issue yet.

Answer

I cannot replicate this using Jackson 2.11.3 which is pulled in by Spring Boot 2.3.5

The following test correctly serializes with camelCase.

public class DualNamingStrategy {

    public static void main(String[] args) throws JsonProcessingException {
        final String json = """
                {
                  "model_id": "mdl_secrecy",
                  "model_name": "secrecy",
                  "model_version": "3.4.1",
                  "section_score": "0.19208406",
                  "risk_status": "low"
                }
                """;
        final ObjectMapper mapper = new ObjectMapper();
        final ModelExplanation explanation = mapper.readValue(json, ModelExplanation.class);
        final String serialised = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(explanation);
        System.out.println(serialised);
    }

}

Output:

{
  "modelId" : "mdl_secrecy",
  "modelName" : "secrecy",
  "modelVersion" : "3.4.1",
  "score" : "0.19208406",
  "riskStatus" : "low"
}

Now, that was not an answer so I’ll provide one here, though it’s a workaround since the issue seems to be somewhere else than in the code you’ve posted.

You haven’t told us which version of Java you’re using so I’m assuming it’s 8 or later, if you’re on 7 or earlier this won’t work.

You can drop all the @JsonProperty annotations, add the Jackson Java 8 Parameter Names module (to make up for the removed annotations) and employ two ObjectMappers with different naming strategies:

public class DifferentNamingStrategies {

    public static void main(String[] args) throws JsonProcessingException {
        final String json = "{n" +
                            "  "model_id": "mdl_secrecy",n" +
                            "  "model_name": "secrecy",n" +
                            "  "model_version": "3.4.1",n" +
                            "  "section_score": "0.19208406",n" +
                            "  "risk_status": "low"n" +
                            "}n";

        // Default object mapper, this has camelCase just like the default in Spring
        final ObjectMapper camelMapper = new ObjectMapper()
                // This allows us to remove the @JsonProperty annotations from the constructor parameters
                .registerModule(new ParameterNamesModule());

        // Custom object mapper that we use to deserialise JSON with snake_case
        final ObjectMapper snakeMapper = new ObjectMapper()
                .registerModule(new ParameterNamesModule())
                .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

        final ModelExplanation explanation = snakeMapper.readValue(json, ModelExplanation.class);
        final String serialised = camelMapper.writerWithDefaultPrettyPrinter().writeValueAsString(explanation);
        System.out.println(serialised);
    }

}
Advertisement