Reputation: 11723
I have a JSON structure which contains maps of strings to object:
{
"domains": {
"ldap": {
"users": {
"fwalther": {
"firstName": "Fritz",
"lastName": "Walther"
},
// ...
}
}
},
// ...
}
I want to deserialize this structure to Domain
and User
objects using Jackson, and I want from each user have a back-reference to it's map key (which is the user ID) and to the Domain
container. Either of this works, but it fails if I try to get both back-references at once.
Java classes with @JsonManagedReference
and @JsonBackReference
:
public class Domain {
@JsonManagedReference
@JsonDeserialize(contentUsing = UserDeserializer.class)
private Map<String, User> users;
public Map<String, User> getUsers() {
return users;
}
}
public class User {
@JsonBackReference
private Domain domain;
String userId;
private String firstName;
private String lastName;
// ... getters
}
Custom deserializer to get the map key:
public class UserDeserializer extends JsonDeserializer<User> {
@Override
public User deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String key = p.getCurrentName();
User result = p.readValueAs(User.class);
result.userId = key;
return result;
}
}
Both mechanisms, i.e. the @JsonManagedReference
/@JsonBackReference
pair and @JsonDeserialize
with the custom deserializer, work if I activate only one of them. But if I combine the mechanisms (as shown in the code above), I get the following exception when parsing the JSON:
java.lang.IllegalArgumentException: Cannot handle managed/back reference 'defaultReference': type: value deserializer of type org.example.UserDeserializer does not support them
at com.fasterxml.jackson.databind.JsonDeserializer.findBackReference(JsonDeserializer.java:366) ~[jackson-databind-2.9.8.jar:2.9.8]
at com.fasterxml.jackson.databind.deser.std.ContainerDeserializerBase.findBackReference(ContainerDeserializerBase.java:104) ~[jackson-databind-2.9.8.jar:2.9.8]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase._resolveManagedReferenceProperty(BeanDeserializerBase.java:786) ~[jackson-databind-2.9.8.jar:2.9.8]
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:487) ~[jackson-databind-2.9.8.jar:2.9.8]
...
Looking at the code where the exception is thrown, I see that I'd need to implement the findBackReference
in my custom deserializer, but I have no clue how, and I couldn't find and information on this either. Any ideas?
Or are there other ways to get both the map key and back reference to the containing object?
Upvotes: 2
Views: 1501
Reputation: 11723
With the help of this answer, I found the solution: The custom deserializer needs to be based on the default deserializer, which implements the back references mechanism correctly.
This is a little more complicated than just inheriting from the right base class. Instead, you need to get hold of the (fully configured) default deserializer instance through a custom BeanDeserializerModifier
, and then pass this instance to your subclass of DelegatingDeserializer
:
public ObjectMapper getMapperWithCustomDeserializer() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc, JsonDeserializer<?> defaultDeserializer) {
if (beanDesc.getBeanClass() == User.class) {
return new UserDeserializer(defaultDeserializer);
} else {
return defaultDeserializer;
}
}
});
objectMapper.registerModule(module);
return objectMapper;
}
The custom deserializer then would need to look like this:
public class UserDeserializer extends DelegatingDeserializer {
public UserDeserializer(JsonDeserializer<?> delegate) {
super(delegate);
}
@Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegate) {
return new UserDeserializer(newDelegate);
}
@Override
public User deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
String key = p.getCurrentName();
User result = (User) super.deserialize(p, ctxt);
result.userId = key;
return result;
}
}
Finally, you need to remove the @JsonDeserialize
annotation. Then, the custom deserializer and the @JsonBackReference
should work.
Upvotes: 2