I managed to configure Jackson to serialize a class without any getters or annotations on the private fields inside the class by just using
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
But I don't manage to make a JSON string of this non-static class to get deserialized into an object without any parameterized constructor or setters. Is this possible at all - I think it should through reflection but I don't get exactly how...
Here are 2 tests which need to pass to achieve what I need:
public class ObjectMapperTest {
private ObjectMapper mapper;
@Before
public void init() {
mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
@Test
public void serialize() throws Exception {
Payload payloadToSerialize = new Payload();
payloadToSerialize.data = "testData";
String serializedPayload = mapper.writeValueAsString(payloadToSerialize);
assertThat(serializedPayload, is("{\"data\":\"testData\"}"));
// --> OK
}
@Test
public void deserialize() throws Exception {
Payload deserializedPayload = mapper.readValue("{\"data\":\"testData\"}", Payload.class);
assertThat(deserializedPayload.data, is("testData"));
// com.fasterxml.jackson.databind.JsonMappingException:
// No suitable constructor found for type [simple type, class ...ObjectMapperTest$Payload]:
// can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
// at [Source: {"data":"testData"}; line: 1, column: 2]
}
public class Payload {
private String data;
public Payload() {
// empty constructor for Jackson
}
}
}
Making the Payload class static would fix the test but static classes are not an option for me as I am not working with inner payload classes in the project. Any ideas how to fix it through object mapper configuration change?
EDIT As I am using the Jackson object mapper in a Spring MVC application to serialize / deserialize under the hood I need a solution which changes or extends the object mapper configuration only.
Deserialization Without No-arg Constructors. By default, Jackson recreates data objects by using no-arg constructors.
Jackson can handle serialization or deserialization without getter or setter methods for a public field.
Need of Default ConstructorBy default, Java provides a default constructor(if there's no parameterized constructor) which is used by Jackson to parse the response into POJO or bean classes.
Jackson can't serialize private fields - without accessor methods - with its default settings. PrivatePerson has two private fields with no public accessors.
You can write your own deserializer to parse the JSON and create the instance of Payload
, and then set the data
value using reflection.
Exemple :
@Before
public void init() {
mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
mapper.registerModule(
new SimpleModule()
.addDeserializer(Payload.class, new JsonDeserializer<Payload>() {
@Override
public Payload deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException {
JsonNode obj = parser.readValueAsTree(); // Read the JSON as a node
Payload payload = new Payload();
if (obj.isObject() && obj.has("data")) { // The node is an object and has a "data" field
try {
// Use reflection to set the value
Field dataField = payload.getClass().getDeclaredField("data");
dataField.setAccessible(true);
dataField.set(payload, obj.get("data").asText());
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
throw new IOException("Reflection error", ex);
}
}
return payload;
}
}));
}
Edit: If you want something more "generic" you can try to create the instance yourself and change the accessibility of all the fields. Then you tell Jackson to update the values using the JSON.
public <T> T deserialize(final String json, final T instance) throws Exception {
for (Field field : instance.getClass().getDeclaredFields()) {
field.setAccessible(true);
}
mapper.readerForUpdating(instance).readValue(json);
return instance;
}
@Test
public void deserializeUpdate() throws Exception {
Payload deserializedPayload = deserialize("{\"data\":\"testData\"}", new Payload());
assertThat(deserializedPayload.data, is("testData"));
}
I tested this on your Payload class, maybe it doesn't work on more complex objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With