Estoy tratando de deserializar una instancia de esta clase usando Jackson 1.9.10:
public class Person {
@JsonCreator
public Person(@JsonProperty("name") String name,
@JsonProperty("age") int age) {
// ... person with both name and age
}
@JsonCreator
public Person(@JsonProperty("name") String name) {
// ... person with just a name
}
}
Cuando intento esto, obtengo lo siguiente
Creadores basados en propiedades en conflicto: ya tenía ... {interfaz org.codehaus.jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}], encontrado ..., anotaciones: {interfaz org.codehaus. jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}]
¿Hay alguna forma de deserializar una clase con constructores sobrecargados usando Jackson?
Gracias
Respuestas:
Aunque no está debidamente documentado, solo puede tener un creador por tipo. Puede tener tantos constructores como desee en su tipo, pero solo uno de ellos debe tener una
@JsonCreator
anotación.fuente
EDITAR: He aquí, en una publicación de blog de los mantenedores de Jackson, parece que 2.12 puede ver mejoras con respecto a la inyección del constructor. (La versión actual en el momento de esta edición es 2.11.1)
Esto sigue siendo válido para Jackson databind 2.7.0.
The Jackson
@JsonCreator
anotación 2.5 javadoc o Jackson anotaciones documentación de la gramática ( constructor s y método de fábrica s ) dejar que creen en verdad que uno puede marcar varios constructores.Al observar el código donde se identifican los creadores , parece que Jackson
CreatorCollector
está ignorando los constructores sobrecargados porque solo verifica el primer argumento del constructor .Class<?> oldType = oldOne.getRawParameterType(0); Class<?> newType = newOne.getRawParameterType(0); if (oldType == newType) { throw new IllegalArgumentException("Conflicting "+TYPE_DESCS[typeIndex] +" creators: already had explicitly marked "+oldOne+", encountered "+newOne); }
oldOne
es el primer creador de constructores identificado.newOne
es el creador del constructor sobrecargado.Eso significa que un código como ese no funcionará
@JsonCreator public Phone(@JsonProperty("value") String value) { this.value = value; this.country = ""; } @JsonCreator public Phone(@JsonProperty("country") String country, @JsonProperty("value") String value) { this.value = value; this.country = country; } assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336"); // raise error here assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336");
Pero este código funcionará:
@JsonCreator public Phone(@JsonProperty("value") String value) { this.value = value; enabled = true; } @JsonCreator public Phone(@JsonProperty("enabled") Boolean enabled, @JsonProperty("value") String value) { this.value = value; this.enabled = enabled; } assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336"); assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");
Esto es un poco engañoso y puede que no sea a prueba de futuro .
La documentación es vaga sobre cómo funciona la creación de objetos; Sin embargo, de lo que obtengo del código, es que es posible mezclar diferentes métodos:
Por ejemplo, se puede tener un método de fábrica estático anotado con
@JsonCreator
@JsonCreator public Phone(@JsonProperty("value") String value) { this.value = value; enabled = true; } @JsonCreator public Phone(@JsonProperty("enabled") Boolean enabled, @JsonProperty("value") String value) { this.value = value; this.enabled = enabled; } @JsonCreator public static Phone toPhone(String value) { return new Phone(value); } assertThat(new ObjectMapper().readValue("\"+336\"", Phone.class).value).isEqualTo("+336"); assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336"); assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");
Funciona pero no es ideal. Al final, podría tener sentido, por ejemplo, si el json es así de dinámico, entonces quizás uno debería buscar usar un constructor delegado para manejar las variaciones de carga útil de manera mucho más elegante que con múltiples constructores anotados.
También tenga en cuenta que Jackson ordena a los creadores por prioridad , por ejemplo, en este código:
// Simple @JsonCreator public Phone(@JsonProperty("value") String value) { this.value = value; } // more @JsonCreator public Phone(Map<String, Object> properties) { value = (String) properties.get("value"); // more logic } assertThat(new ObjectMapper().readValue("\"+336\"", Phone.class).value).isEqualTo("+336"); assertThat(new ObjectMapper().readValue("{\"value\":\"+336\"}", Phone.class).value).isEqualTo("+336"); assertThat(new ObjectMapper().readValue("{\"value\":\"+336\",\"enabled\":true}", Phone.class).value).isEqualTo("+336");
Esta vez Jackson no generará un error, pero Jackson solo usará el constructor delegado
Phone(Map<String, Object> properties)
, lo que significa quePhone(@JsonProperty("value") String value)
nunca se usa.fuente
Si entendí bien lo que está tratando de lograr, puede resolverlo sin una sobrecarga de constructores .
Si solo desea poner valores nulos en los atributos que no están presentes en un JSON o un mapa, puede hacer lo siguiente:
@JsonIgnoreProperties(ignoreUnknown = true) public class Person { private String name; private Integer age; public static final Integer DEFAULT_AGE = 30; @JsonCreator public Person( @JsonProperty("name") String name, @JsonProperty("age") Integer age) throws IllegalArgumentException { if(name == null) throw new IllegalArgumentException("Parameter name was not informed."); this.age = age == null ? DEFAULT_AGE : age; this.name = name; } }
Ese fue mi caso cuando encontré tu pregunta. Me tomó algo de tiempo descubrir cómo resolverlo, tal vez eso es lo que estabas intentando hacer. La solución @Brice no funcionó para mí.
fuente
Si no le importa trabajar un poco más, puede deserializar la entidad manualmente:
@JsonDeserialize(using = Person.Deserializer.class) public class Person { public Person(@JsonProperty("name") String name, @JsonProperty("age") int age) { // ... person with both name and age } public Person(@JsonProperty("name") String name) { // ... person with just a name } public static class Deserializer extends StdDeserializer<Person> { public Deserializer() { this(null); } Deserializer(Class<?> vc) { super(vc); } @Override public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { JsonNode node = jp.getCodec().readTree(jp); if (node.has("name") && node.has("age")) { String name = node.get("name").asText(); int age = node.get("age").asInt(); return new Person(name, age); } else if (node.has("name")) { String name = node.get("name").asText(); return new Person("name"); } else { throw new RuntimeException("unable to parse"); } } } }
fuente