JsonMappingException: Nie znaleziono odpowiedniego konstruktora dla typu [typ prosty, klasa]: nie można utworzyć instancji z obiektu JSON

438

Podczas próby otrzymania żądania JSON i przetworzenia go pojawia się następujący błąd:

org.codehaus.jackson.map.JsonMappingException: Nie znaleziono odpowiedniego konstruktora dla typu [typ prosty, klasa com.myweb.ApplesDO]: nie można utworzyć instancji z obiektu JSON (trzeba dodać / włączyć informacje o typie?)

Oto JSON, który próbuję wysłać:

{
  "applesDO" : [
    {
      "apple" : "Green Apple"
    },
    {
      "apple" : "Red Apple"
    }
  ]
}

W kontrolerze mam następującą sygnaturę metody:

@RequestMapping("showApples.do")
public String getApples(@RequestBody final AllApplesDO applesRequest){
    // Method Code
}

AllApplesDO to opakowanie ApplesDO:

public class AllApplesDO {

    private List<ApplesDO> applesDO;

    public List<ApplesDO> getApplesDO() {
        return applesDO;
    }

    public void setApplesDO(List<ApplesDO> applesDO) {
        this.applesDO = applesDO;
    }
}

Jabłka DO:

public class ApplesDO {

    private String apple;

    public String getApple() {
        return apple;
    }

    public void setApple(String appl) {
        this.apple = apple;
    }

    public ApplesDO(CustomType custom){
        //constructor Code
    }
}

Myślę, że Jackson nie jest w stanie przekonwertować JSON na obiekty Java dla podklas. Proszę o pomoc w parametrach konfiguracyjnych dla Jacksona w celu konwersji JSON na obiekty Java. Korzystam z Spring Framework.

EDYCJA: Uwzględniono główny błąd, który powoduje ten problem w powyższej przykładowej klasie - Proszę spojrzeć na zaakceptowaną odpowiedź na rozwiązanie.

Lucky Murari
źródło
2
Nie widzę żadnych podklas w powyższym kodzie, czy ten kod jest tym, czego próbujesz, czy tworzysz prostszy przykład?
gkamal
Dodałem odpowiedź z dodatkowym wyjaśnieniem, jak to działa. Zasadniczo musisz zdawać sobie sprawę, że Java nie zachowuje nazw argumentów metod w środowisku wykonawczym.
Vlasec,

Odpowiedzi:

565

W końcu zrozumiałem na czym polega problem. To nie jest problem z konfiguracją Jacksona, jak wątpiłem.

W rzeczywistości problem dotyczył klasy ApplesDO :

public class ApplesDO {

    private String apple;

    public String getApple() {
        return apple;
    }

    public void setApple(String apple) {
        this.apple = apple;
    }

    public ApplesDO(CustomType custom) {
        //constructor Code
    }
}

Dla klasy zdefiniowano niestandardowego konstruktora, co czyni go domyślnym konstruktorem. Wprowadzenie fałszywego konstruktora spowodowało błąd, aby odejść:

public class ApplesDO {

    private String apple;

    public String getApple() {
        return apple;
    }

    public void setApple(String apple) {
        this.apple = apple;
    }

    public ApplesDO(CustomType custom) {
        //constructor Code
    }

    //Introducing the dummy constructor
    public ApplesDO() {
    }

}
Lucky Murari
źródło
Czy mogę zapytać, skąd pochodzi CustomType. Próbuję takiej struktury, ale jestem zupełnie nowy w Javie.
andho
181
Jacksona można używać z klasami wewnętrznymi (zagnieżdżonymi), w tym przypadku serializacja działa dobrze. Jedyną przeszkodą jest to, że klasa wewnętrzna musi być oznaczona jako „statyczna”, aby deserializacja działała poprawnie. Zobacz wyjaśnienie tutaj: cowtowncoder.com/blog/archives/2010/08/entry_411.html
jpennell
3
Czy ktoś mógłby wyjaśnić, dlaczego tak się dzieje? Miałem bardzo podobny błąd. Myślałem, że wszystkie właściwe konstruktory są na miejscu, ale nie można deserializować. Działało to dopiero po dodaniu fałszywego konstruktora, po przeczytaniu tego postu.
user8658912
6
@Suman Nie nazwałbym tego fałszywym konstruktorem - to tylko domyślny konstruktor. Jest nie tylko doskonale poprawny, ale wymagany do wielu rodzajów przetwarzania typu java bean. (I, oczywiście, to mnie
potknęło
2
Jeśli nie chcesz dodawać domyślnego konstruktora (np. Gdy masz do czynienia z niezmiennymi obiektami). Będziesz musiał powiedzieć, jakiego konstruktora lub metody fabryki użyć, aby utworzyć instancję obiektu za pomocą adnotacji JsonCreator.
Rahul
375

Dzieje się tak z następujących powodów:

  1. twoją klasę wewnętrzną należy zdefiniować jako statyczną

    private static class Condition {  //jackson specific    
    }
  2. Możliwe, że nie masz domyślnego konstruktora w swojej klasie ( AKTUALIZACJA: Wydaje się, że tak nie jest)

    private static class Condition {
        private Long id;
    
        public Condition() {
        }
    
        // Setters and Getters
    }
  3. Możliwe, że Twoi Settery nie są odpowiednio zdefiniowani lub nie są widoczni (np. Seter prywatny)

azerafati
źródło
95
klasa statyczna zrobiła różnicę w moim przypadku. Dzięki!
jalogar
3
I oczywiście nie musisz deklarować pustego domyślnego konstruktora bez argumentów, Java zrobi to za Ciebie ! (O ile nie zdefiniujesz żadnych innych konstruktorów.)
Jonik
1
@Jonik, racja! moja odpowiedź jest stara, jeśli dobrze pamiętam, ponieważ Jackson używa refleksji, aby dostać się do klasy wewnętrznej, wydaje mi się, że konieczne było zdefiniowanie domyślnego konstruktora (może nie być tak w przypadku nowszych wersji), ale ponieważ nie jestem na pewno możesz mieć rację.
azerafati,
6
Tak, mam doświadczenie z Jackson 2.4.4: rzeczywiście domyślny domyślny konstruktor Javy wystarczy. Musisz jawnie napisać konstruktor no-args tylko wtedy, gdy zdefiniowałeś inne konstruktory, które pobierają argumenty (tj. Gdy Java nie generuje dla ciebie takiego argumentu).
Jonik
2
Również u mnie klasa statyczna uratowała dzień.
Simon
58

Chciałbym dodać do tego inne rozwiązanie, które nie wymaga fałszywego konstruktora. Ponieważ fałszywe konstruktory są nieco niechlujne, a następnie mylące. Możemy zapewnić bezpieczny konstruktor i dodając adnotacje do argumentów konstruktora, umożliwiamy Jacksonowi określenie odwzorowania między parametrem konstruktora a polem.

więc następujące będą również działać. Uwaga: ciąg znaków w adnotacji musi być zgodny z nazwą pola.

import com.fasterxml.jackson.annotation.JsonProperty;
public class ApplesDO {

        private String apple;

        public String getApple() {
            return apple;
        }

        public void setApple(String apple) {
            this.apple = apple;
        }

        public ApplesDO(CustomType custom){
            //constructor Code
        }

        public ApplesDO(@JsonProperty("apple")String apple) {
        }

}
PiersyP
źródło
To rozwiązanie załatwiło sprawę. Chciałbym tylko wspomnieć o tym, miałem konstruktora, ale nazwa parametrów była inna niż parametrów instancji, więc nie można było odwzorować. Dodanie adnotacji rozwiązało problem, ale zmiana nazwy parametrów prawdopodobnie również zadziałałaby.
Fico,
Co jeśli parametr konstruktora nie znajduje się w odpowiedzi? Czy można go wstrzyknąć w inny sposób?
Eddie Jaoude,
Jedyne istotne dane, które są tutaj wysyłane, to jabłko String, które jest odpowiedzią.
PiersyP,
1
Przydało mi się to, ponieważ chciałem, aby mój obiekt był niezmienny, więc konstruktor nie był opcją.
Jonathan Pullano,
W moim przypadku wymaga to również dodania @JsonCreatorkonstruktora za pomocą @JsonProperty.
m1ld 27.04.16
31

Kiedy natknąłem się na ten problem, był to wynik próby użycia klasy wewnętrznej do pełnienia funkcji DO. Konstrukcja klasy wewnętrznej (po cichu) wymagała wystąpienia klasy obejmującej - która nie była dostępna dla Jacksona.

W takim przypadku przeniesienie klasy wewnętrznej do własnego pliku .java rozwiązało problem.

jmarks
źródło
6
Podczas gdy przenoszenie klasy wewnętrznej do własnego pliku .java działa, dodanie modyfikatora statycznego rozwiązuje również problem, jak wspomniano w odpowiedzi @ bludream.
jmarks
20

Zasadniczo ten błąd występuje, ponieważ nie tworzymy domyślnego konstruktora, ale w moim przypadku problem pojawił się tylko dlatego, że utworzyłem używaną klasę obiektów w klasie nadrzędnej. To zmarnowało mój cały dzień.

alok
źródło
Wystarczy stworzyć zagnieżdżoną klasę static.
lenistwo
13

Reguła kciuka : dodaj domyślny konstruktor dla każdej klasy użytej jako klasa odwzorowania. Przegapiłeś to i problem się pojawia!
Wystarczy dodać domyślny konstruktor i powinien on działać.

Badal
źródło
ładna odpowiedź. dziękuję. ocaliłeś mój dzień.
Steve
9

Czy możesz przetestować tę strukturę? Jeśli dobrze pamiętam, możesz użyć tego w ten sposób:

{
    "applesRequest": {
        "applesDO": [
            {
                "apple": "Green Apple"
            },
            {
                "apple": "Red Apple"
            }
        ]
    }
}

Po drugie, dodaj domyślny konstruktor do każdej klasy, która również może pomóc.

danny.lesnik
źródło
Nie działa: pojawia się następujący błąd: „org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Pole nierozpoznane„ applesRequest ”(klasa com.smartshop.dao.AllApplesDO), nieoznaczony jako nieusuwalny”
Lucky Murari,
Wcześniej przynajmniej nie powodował błędu dla AllApplesDO i rzuca tylko dla zamkniętej klasy .. teraz rzuca dla samej pierwszej klasy
Lucky Murari
Potrzebował domyślnego konstruktora. Dzięki!
Planky
czy nie należy tego wybierać jako PRAWIDŁOWEJ odpowiedzi?
szybki ząb
7

Musisz stworzyć atrapę pustego konstruktora w naszej klasie modeli, więc podczas mapowania jsona ustawia się go metodą setter.

Suresh Patil
źródło
To jest poprawka.
David Kobia
To samo dotyczy mnie. Miałem wiele różnych konstruktorów dla mojego obiektu, więc właśnie stworzyłem kolejny pusty, który najwyraźniej jest wykorzystywany przez Jacksona.
Alessandro Roaro,
5

Jeśli zaczniesz dodawać adnotacje do konstruktora, musisz dodać adnotacje do wszystkich pól.

Uwaga: moje pole Staff.name jest odwzorowane na „ANOTHER_NAME” w ciągu JSON.

     String jsonInString="{\"ANOTHER_NAME\":\"John\",\"age\":\"17\"}";
     ObjectMapper mapper = new ObjectMapper();
     Staff obj = mapper.readValue(jsonInString, Staff.class);
     // print to screen

     public static class Staff {
       public String name;
       public Integer age;
       public Staff() {         
       }        

       //@JsonCreator - don't need this
       public Staff(@JsonProperty("ANOTHER_NAME") String   n,@JsonProperty("age") Integer a) {
        name=n;age=a;
       }        
    }
Wir
źródło
4

Musisz zdać sobie sprawę z dostępnych opcji Jacksona na deserializację. W Javie nazwy argumentów metod nie występują w skompilowanym kodzie. Dlatego Jackson zazwyczaj nie może używać konstruktorów do tworzenia dobrze zdefiniowanego obiektu ze wszystkim, co już zostało ustawione.

Tak więc, jeśli istnieje pusty konstruktor i istnieją również setery, używa pustego konstruktora i seterów. Jeśli nie ma seterów, używa się do tego trochę mrocznej magii (odbicia).

Jeśli chcesz użyć konstruktora z Jacksonem, musisz użyć adnotacji wspomnianych przez @PiersyP w jego odpowiedzi. Możesz także użyć wzorca konstruktora. Jeśli napotkasz wyjątki, powodzenia. Obsługa błędów w Jacksonie jest do bani, trudno zrozumieć, że bełkot w komunikatach o błędach.

Vlasec
źródło
Powodem, dla którego potrzebujesz „domyślnego konstruktora bez argumentów” FooClass (), jest prawdopodobnie to, że Spring postępuje zgodnie ze specyfikacją JavaBean, która wymaga, aby działał on w celu automatycznego zestawiania i odłączania podczas szeregowania i deserializacji obiektów.
atom88
Cóż, serializacja Java i deserializacja do strumienia binarnego nie jest zresztą tym, o co chodzi. Dobrze więc, że Jackson oferuje wiele wzorców do zastosowania w deserializacji. Szczególnie podoba mi się wzorzec konstruktora, ponieważ pozwala on, aby powstały obiekt był niezmienny.
Vlasec
3

Jeśli chodzi o ostatnią publikację, miałem ten sam problem, gdy wygenerowanie problemu spowodowało użycie Lombok 1.18. *.

Moim rozwiązaniem było dodanie @NoArgsConstructor (konstruktor bez parametrów), ponieważ @Data domyślnie zawiera @RequiredArgsConstructor (konstruktor z parametrami).

Dokumentacja lombok https://projectlombok.org/features/all

To rozwiązałoby problem:

package example.counter;

import javax.validation.constraints.NotNull;

import lombok.Data;

@Data
@NoArgsConstructor
public class CounterRequest {
    @NotNull
    private final Integer int1;

    @NotNull
    private final Integer int2;
}
Felipe Ceballos
źródło
0

Problemem może być także awaria niestandardowych serializatorów / dezrializatorów firmy jackson. Chociaż to nie twoja sprawa, warto o tym wspomnieć.

Napotkałem ten sam wyjątek i tak właśnie było.

Artem Novikov
źródło
0

Dla mnie to działało, ale aktualizacja bibliotek spowodowała pojawienie się tego problemu. Problemem było zorganizowanie takiej klasy:

package example.counter;

import javax.validation.constraints.NotNull;

import lombok.Data;

@Data
public class CounterRequest {
    @NotNull
    private final Integer int1;

    @NotNull
    private final Integer int2;
}

Za pomocą lombok:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.0</version>
</dependency>

Wracając do

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
</dependency>

Naprawiono problem. Nie jestem pewien, dlaczego, ale chciałem to udokumentować na przyszłość.

eis
źródło