Próbuję deserializować instancję tej klasy przy użyciu Jacksona 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
}
}
Kiedy próbuję tego, otrzymuję następujące informacje
Sprzeczne twórcy oparte na właściwościach: już mieli ... {interfejs org.codehaus.jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}], napotkano ..., adnotacje: {interface org.codehaus. jackson.annotate.JsonCreator @ org.codehaus.jackson.annotate.JsonCreator ()}]
Czy istnieje sposób na deserializację klasy z przeciążonymi konstruktorami przy użyciu Jacksona?
Dzięki
Odpowiedzi:
Chociaż nie jest to odpowiednio udokumentowane, możesz mieć tylko jednego twórcę na typ. Możesz mieć dowolną liczbę konstruktorów w swoim typie, ale tylko jeden z nich powinien mieć
@JsonCreator
adnotację.źródło
EDYCJA : Oto w poście na blogu opiekunów Jacksona wygląda na to, że 2.12 może zobaczyć ulepszenia w odniesieniu do wstrzykiwania konstruktora. (Aktualna wersja w momencie tej edycji to 2.11.1)
To nadal jest prawdą w przypadku indeksu Jackson w wersji 2.7.0.
The Jackson
@JsonCreator
adnotacja 2,5 javadoc lub Jacksona adnotacje dokumentacja gramatyki ( konstruktor s i metoda fabryki s ) Pozwól uwierzyć, że rzeczywiście można oznaczyć wielu konstruktorów.Patrząc na kod, w którym zidentyfikowani są twórcy , wygląda na to, że Jackson
CreatorCollector
ignoruje przeciążone konstruktory, ponieważ sprawdza tylko pierwszy argument konstruktora .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
jest pierwszym zidentyfikowanym twórcą konstruktora.newOne
jest przeciążonym twórcą konstruktora.Oznacza to, że taki kod nie zadziała
@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");
Ale ten kod zadziała:
@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");
Jest to trochę hakerskie i może nie być przyszłościowe .
Dokumentacja jest niejasna o tym, jak działa tworzenie obiektów; z tego, co wyciągam z kodu, wynika jednak, że można mieszać różne metody:
Na przykład można mieć statyczną metodę fabryki z adnotacją
@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");
Działa, ale nie jest idealny. Ostatecznie może to mieć sens, np. Jeśli JSON jest tak dynamiczny, to może należałoby spróbować użyć konstruktora delegata do obsługi zmian ładunku znacznie bardziej elegancko niż w przypadku wielu konstruktorów z adnotacjami.
Zwróć też uwagę, że Jackson porządkuje twórców według priorytetów , na przykład w tym kodzie:
// 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");
Tym razem Jackson nie zgłosi błędu, ale Jackson użyje tylko konstruktora delegata
Phone(Map<String, Object> properties)
, co oznacza, żePhone(@JsonProperty("value") String value)
nigdy nie zostanie użyty.źródło
Jeśli mam rację, co próbujesz osiągnąć, możesz rozwiązać to bez przeciążania konstruktora .
Jeśli chcesz po prostu umieścić wartości null w atrybutach nieobecnych w JSON lub Mapie, możesz wykonać następujące czynności:
@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; } }
To był mój przypadek, kiedy znalazłem twoje pytanie. Zajęło mi trochę czasu, zanim wymyśliłem, jak to rozwiązać, może to właśnie chciałeś zrobić. Rozwiązanie @Brice nie działa dla mnie.
źródło
Jeśli nie masz nic przeciwko wykonaniu trochę więcej pracy, możesz ręcznie deserializować encję:
@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"); } } } }
źródło