I have the following Java classes:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
@JsonSubTypes.Type(ChildAsJsonSubType.class),
})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ParentClass {
private String recipientUserId;
private String initiatorUserId;
}
@Data
@NoArgsConstructor
public class ChildAsJsonSubType extends ParentClass {
public ChildAsJsonSubType(String recipientUserId, String initiatorUserId) {
super(recipientUserId, initiatorUserId);
}
}
// not declareted in ParentClass's JsonSubTypes
@Data
@NoArgsConstructor
public class ChildAsJNOTsonSubType extends ParentClass {
public ChildAsJNOTsonSubType(String recipientUserId, String initiatorUserId) {
super(recipientUserId, initiatorUserId);
}
}
Now I have the following Junit class to test deserialization:
class ParentClassTest {
private final ObjectMapper mapper = new ObjectMapper();
@DisplayName("Deserialize JSON representing a ParentClass subclass as instance of this subclass")
@ParameterizedTest
@ValueSource(classes = {
ChildAsJsonSubType.class,
ChildAsJNOTsonSubType.class
})
void testDeserializeSubclassesAsParentClassSubclass(Class<? extends ParentClass> ParentClassSubclass) throws JsonProcessingException {
String recipientUserId = "0123456789abcdef";
String initiatorUserId = "fedcba9876543210";
var json = String.format("{\"@type\":\"%s\",\"recipientUserId\":\"%s\",\"initiatorUserId\":\"%s\"}",
ParentClassSubclass.getSimpleName(), recipientUserId, initiatorUserId);
ParentClass result = mapper.readValue(json, ParentClassSubclass);
assertThat(result)
.extracting(ParentClass::getRecipientUserId,
ParentClass::getInitiatorUserId)
.containsExactly(recipientUserId, initiatorUserId);
}
@DisplayName("Deserialize JSON representing a ParentClass subclass as instance of ParentClass")
@ParameterizedTest
@ValueSource(classes = {
ChildAsJsonSubType.class,
ChildAsJNOTsonSubType.class
})
void testDeserializeSubclassesAsParentClass(Class<? extends ParentClass> ParentClassSubclass) throws JsonProcessingException {
String recipientUserId = "0123456789abcdef";
String initiatorUserId = "fedcba9876543210";
var json = String.format("{\"@type\":\"%s\",\"recipientUserId\":\"%s\",\"initiatorUserId\":\"%s\"}",
ParentClassSubclass.getSimpleName(), recipientUserId, initiatorUserId);
ParentClass result = mapper.readValue(json, ParentClass.class);
assertThat(result)
.extracting(ParentClass::getRecipientUserId,
ParentClass::getInitiatorUserId)
.containsExactly(recipientUserId, initiatorUserId);
}
}
When I run the 1st test (testDeserializeSubclassesAsParentClassSubclass), It is Ok. When I run the 2nd one (testDeserializeSubclassesAsParentClass) for ChildAsJNOTsonSubType, I got this error:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'ChildAsJNOTsonSubType' as a subtype of
ParentClass: known type ids = [ChildAsJsonSubType, ParentClass] at [Source: (String)"{"@type":"ChildAsJNOTsonSubType","recipientUserId":"0123456789abcdef","initiatorUserId":"fedcba9876543210"}"; line: 1, column: 10]
Of course, the solution would be simply add ChildAsJNOTsonSubType in ParentClass JsonSubTypes.
I must say that originally ParentClass had @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS). However I had to change due to SonarQube problem java:S4544 (Using unsafe Jackson deserialization configuration is security-sensitive).
The problem, however is that there are ParentClass children classes outside its original module. i.e. in another JAR file. If I try run the tests above with these subclasses outside the original JAR of ParentClass, I probably I will get the same error as ChildAsJNOTsonSubType.
Therefore how I can emulate @JsonSubTypes / @JsonSubTypes.Type behavior for children classes that are not in the same JAR as parent class. May be some special annotation? Or a StdDeserializer implementation?