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