Please see update below on a quick way to reproduce exception
The following two classes form a circular reference:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "code")
@Value
public class Department {
String code;
@ToString.Exclude
List<Employee> employees;
}
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class,
property="id")
@Value
public class Employee {
String name;
Department department;
}
In a producer web service (using Jackson via Spring @RestController
), these classes get serialized into JSON. Here's an example response returning a Department
object:
{
"code": "HR",
"employees": [
{
"id": 1,
"name": "Bob",
"department": "HR"
},
{
"id": 2,
"name": "Sally",
"department": "HR"
}
]
}
In a consumer service (which is a @FeignClient
of the producer), the data is attempted to be deserialized in the exact same two classes. However, I get the following exception:
Error while extracting response for type [class com.example.Department] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No Object Id found for an instance of `com.example.Employee`, to assign to property 'id'; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: No Object Id found for an instance of `com.example.Employee`, to assign to property 'id' at [Source: (PushbackInputStream); line: 1, column: 65] (through reference chain: com.example.Department["employees"]->java.util.ArrayList[0])
feign.codec.DecodeException: Error while extracting response for type [class com.example.Department] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No Object Id found for an instance of `com.example.Employee`, to assign to property 'id'; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: No Object Id found for an instance of `com.example.Employee`, to assign to property 'id'
at [Source: (PushbackInputStream); line: 1, column: 65] (through reference chain: com.example.Department["employees"]->java.util.ArrayList[0])
I'm using lombok.anyConstructor.addConstructorProperties=true to support constructor property setting ala this post. I get the same error if I directly use @ConstructorProperties
so it's not a Lombok issue.
When I make the class mutable (e,g, replace lombok @Value
with @Data
) or don't have circular references, everything works fine. But I would like my classes to be immutable.
Is it possible to use @JsonIdentityInfo to support circular references and deserialize into immutable classes?
Update
This is easy to reproduce. Create a project with the two classes noted above.
Using this gradle build file:
plugins {
id 'java-library'
}
repositories {
jcenter()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.0'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.12.0'
testImplementation 'junit:junit:4.12'
}
Then, run this simple test:
public class DeserializationTest {
@Test
public void testDeserializationIntoImmutableClasses() throws JsonProcessingException {
List<Employee> employees = new ArrayList<>();
Department hr = new Department("HR", employees);
Employee bob = new Employee("Bob", hr);
Employee sally = new Employee("Sally", hr);
employees.add(bob);
employees.add(sally);
ObjectMapper om = new ObjectMapper();
//serialize object referenced by object variable hr
String hrJson = om.writeValueAsString(hr);
//test fails by throwing
// com.fasterxml.jackson.databind.exc.MismatchedInputException:
// No Object Id found for an instance of `com.example.Employee`, to
// assign to property 'id' at
// [Source: (String)"
// {"code":"HR","employees":
// [{"id":1,"name":"Bob","department":"HR"},
// {"id":2,"name":"Sally","department":"HR"}]}";
// line: 1, column: 65]
// (through reference chain: com.example.Department["employees"]-
// >java.util.ArrayList[0])
om.readValue(hrJson, Department.class);
}
}
I'll look at adding to GitHub and include the link in a second update.