Skip to content

Instantly share code, notes, and snippets.

@mgius
Last active February 23, 2016 01:15
Show Gist options
  • Save mgius/aa4f2a54fbbcd348196a to your computer and use it in GitHub Desktop.
Save mgius/aa4f2a54fbbcd348196a to your computer and use it in GitHub Desktop.
import static org.junit.Assert.assertTrue;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.junit.Test;
public class JacksonBugTest {
@Test // passes
public void testSerializeDefaultMapper() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Model model = new Model.Builder().withSomeValue("whatever").build();
String json = mapper.writeValueAsString(model);
Model reconstructed = mapper.readValue(json, Model.class);
}
@Test // fails: Unrecognized field "someValue"
public void testSerializeSnakeCaseMapper() throws Exception {
ObjectMapper mapper = new ObjectMapper().setPropertyNamingStrategy(
PropertyNamingStrategy.SNAKE_CASE);
Model model = new Model.Builder().withSomeValue("whatever").build();
String json = mapper.writeValueAsString(model);
// The serialized JSON includes someValue even though we serialized using SNAKE_CASE.
// However, if we change the JsonProperty on the constructor to any other string the field will
// be snake case serialized and this assertion will fail
assertTrue(json.contains("someValue"));
// This deserialize will fail because the mapper is looking for some_value, but someValue has
// been serialized instead.
Model reconstructed = mapper.readValue(json, Model.class);
}
// Use the builder to deserialize
@JsonDeserialize(builder = Model.Builder.class)
public static class Model {
private String someValue;
// Previously we used JsonCreator, and for whatever reason the JsonProperty lingered
// This JsonProperty has an unexpected affect on Serialization.
private Model(@JsonProperty("someValue") String matchingArgument) {
this.someValue = matchingArgument;
}
public String getSomeValue() {
return someValue;
}
public static class Builder {
private String someValue;
public Builder() {
}
public Builder withSomeValue(String someValue) {
this.someValue = someValue;
return this;
}
public Model build() {
return new Model(someValue);
}
}
}
}
package com.oracle.opc.nimbula.osawa.models;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.junit.Test;
public class JacksonBugTest {
private static final ObjectMapper mapper = new ObjectMapper().setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
@Test
public void testSerializeSnakeCaseMapper() throws Exception {
ModelA model = new ModelA("whatever");
String json = mapper.writeValueAsString(model);
// The serialized JSON includes someValue even though we serialized using SNAKE_CASE.
// this is apparently expected because @JsonProperty on the constructor applies to serialization
assertTrue(json.contains("someValue"));
// This deserialize will pass because the mapper is looking for someValue,
// which was overridden implicitly by the JsonProperty in the constructor on ModelA
ModelA reconstructed = mapper.readValue(json, ModelA.class);
}
@Test
public void testSerializeModelB() throws Exception {
ModelB model = new ModelB("whatever");
String json = mapper.writeValueAsString(model);
// The serialized JSON does not include someOtherValue, but instead contains some_value.
// Why does this behave differently depending on the value of @JsonProperty?
// Should not contain some_value, because of constructor annotation?
assertFalse(json.contains("some_value")); // fails
// Should contain someOtherValue, because of constructor annotation?
assertTrue(json.contains("someOtherValue")); // fails
// This also passes, but why should it?
// The explicit JsonProperty on the only constructor doesn't match the json string? And
// there isn't a no-args constructor nor a setter nor is the
ModelB reconstructed = mapper.readValue(json, ModelB.class);
// This passes, so the object is being reconstructed correctly.
assertEquals(model, reconstructed);
}
// Class with JsonProperty that matches field name
public static class ModelA {
private String someValue;
@JsonCreator
private ModelA(@JsonProperty("someValue") String matchingArgument) {
this.someValue = matchingArgument;
}
public String getSomeValue() {
return someValue;
}
}
// Class with JsonProperty that does not match field name
public static class ModelB {
private String someValue;
@JsonCreator
private ModelB(@JsonProperty("someOtherValue") String matchingArgument) {
this.someValue = matchingArgument;
}
public String getSomeValue() {
return someValue;
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment