jackson-databind
jackson-databind copied to clipboard
Two Type fields in serialised string, when using using EXTERNAL_PROPERTY with @JsonTypeName
Describe the bug
I have a class with @JsonTypeName which uses the external field type, I also have a type field in my class. When I am serializing the class object, I am getting two type values.
I have checked that there are issues similar to this issue, but those issues are for PROPERTY and not EXTERNAL_PROPERTY.
Version information Jackson version: 2.7.9
To Reproduce
I have an interface AnimalDetails which is deserialized based on an external property type.
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY, visible = true)
public abstract class AnimalDetails {
}
I have a subclass Dog of class AnimalDetails
package io.harness.connector.jacksonTest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreType;
import com.fasterxml.jackson.annotation.JsonTypeName;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeName("dog")
public class Dog extends AnimalDetails{
String type;
public Dog(String type){
this.type = type;
}
public Dog() {
}
public static DogBuilder builder() {
return new DogBuilder();
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public boolean equals(final Object o) {
if (o == this) return true;
if (!(o instanceof Dog)) return false;
final Dog other = (Dog) o;
if (!other.canEqual((Object) this)) return false;
final Object this$type = this.getType();
final Object other$type = other.getType();
if (this$type == null ? other$type != null : !this$type.equals(other$type)) return false;
return true;
}
protected boolean canEqual(final Object other) {
return other instanceof Dog;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $type = this.getType();
result = result * PRIME + ($type == null ? 43 : $type.hashCode());
return result;
}
public String toString() {
return "Dog(type=" + this.getType() + ")";
}
public static class DogBuilder {
private String type;
DogBuilder() {
}
public Dog.DogBuilder type(String type) {
this.type = type;
return this;
}
public Dog build() {
return new Dog(type);
}
public String toString() {
return "Dog.DogBuilder(type=" + this.type + ")";
}
}
}
The dog also contains a field type and I have used JsonTypeName annotation to say that deserialize into a Dog when the external type is "dog".
When I am trying to serialize the Dog class then I get two type fields.
package io.harness.connector.jacksonTest;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.harness.ng.remote.NGObjectMapperHelper;
import org.slf4j.Logger;
class Scratch {
private static final Logger logger = org.slf4j.LoggerFactory.getLogger(Scratch.class);
public static void main(String[] args) {
Dog dog = Dog.builder().type("GermanShepherd").build();
ObjectMapper objectMapper = new ObjectMapper();
NGObjectMapperHelper.configureNGObjectMapper(objectMapper);
String jsonValue = "";
try {
jsonValue = objectMapper.writeValueAsString(dog);
}catch(Exception ex){
logger.info("Encountered exception ", ex);
}
logger.info(jsonValue);
}
}
Expected behavior Output: {"type":"dog","type":"GermanShepherd"}
ExpectedOutupt: {"type":"GermanShepherd"}
Additional context
Quick question: you mention "EXISTING_PROPERTY", but the test has "EXTERNAL_PROPERTY": I think one or the other is incorrect? If so, could you update title and/or description so these are consistent.
Second: looks like code uses Lombok. That is fine for usage and should not cause issue(s), but for tests it unfortunately makes diagnostics difficult so it would be great to include post-processed class definitions (I think there is a way to get generated sources, or equivalent) for reproduction (databind unit tests can not add Lombok dependency).
Thanks a lot @cowtowncoder for the quick reply. By mistake I wrote EXISTING_PROPERTY, I have fixed the title and the content.
Also updated the code with the post-processed class definitions.
Conceptually the problem is that since type id is "external" (outside of POJO value, in a way), POJO itself really should not have property with same name as that metadata. So you should probably @JsonIgnore it.
It would of course be good for Jackson to avoid such duplicates in this case, use one from POJO, so I'll leave this issue open.
Hey @cowtowncoder, I didn't understand the part why the POJO should not have the same variable type?
I have the following use case, lets say I have an Animal class
public class Animal {
String type; // Can be dog, cat, elephant
String name;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY, visible = true)
AnimalDetails animalDetails;
}
Here we use this type field to decide the type of implementation for AnimalDetails interface.
Also, the dog also contains a type field which is different from this type field
public class Dog extends AnimalDetails{
String type; // can be GermanShepherd, bulldog, dobermann
}
These are two type fields types are at a different level. The input will look like
"{
"type":"dog",
"name":"name",
"animalDetails":{
"type" : "GermanShepherd"
}
}"
I feel that since the two type are independent thus it is a valid use case, please correct me if I am wrong.
Can you also suggest some workaround if I cannot ignore the second type field?
Ah. Thank you for more complete example; I was bit thrown off by Animal/Dog relationship (Dog is sort of like DogDetails). That makes more sense.
The implementation problem comes from joining the type id of POJO (details object) and output of enclosing type (Animal.type) -- currently there is no linkage and because of this, duplication occurs.
I hope this can be solved in the future.