Instanz von java.util.ArrayList kann nicht aus dem START_OBJECT-Token deserialisiert werden

129

Ich versuche, ein Listbenutzerdefiniertes Objekt zu POSTEN . Mein JSON im Anfragetext lautet:

{
    "collection": [
        {
            "name": "Test order1",
            "detail": "ahk ks"
        },
        {
            "name": "Test order2",
            "detail": "Fisteku"
        }
    ]
}

Serverseitiger Code, der die Anforderung verarbeitet:

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;


@Path(value = "/rest/corder")
public class COrderRestService {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response postOrder(Collection<COrder> orders) {
        StringBuilder stringBuilder = new StringBuilder();
        for (COrder c : orders) {
            stringBuilder.append(c.toString());
        }
        System.out.println(stringBuilder);
        return Response.ok(stringBuilder, MediaType.APPLICATION_JSON).build();
    }
}

Entität COrder:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class COrder {
    String name;
    String detail;

    @Override
    public String toString() {
        return "COrder [name=" + name + ", detail=" + detail
                + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
                + ", toString()=" + super.toString() + "]";
    }
}

Aber eine Ausnahme wird geworfen:

SEVERE: Failed executing POST /rest/corder
org.jboss.resteasy.spi.ReaderException: org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
 at [Source: org.apache.catalina.connector.CoyoteInputStream@6de8c535; line: 1, column: 1]
    at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:183)
    at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:88)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:111)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:280)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:234)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:221)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
isah
quelle

Antworten:

156

Das Problem ist der JSON - dies kann standardmäßig nicht in ein deserialisiert werden, Collectionda es sich eigentlich nicht um ein JSON-Array handelt - das würde folgendermaßen aussehen:

[
    {
        "name": "Test order1",
        "detail": "ahk ks"
    },
    {
        "name": "Test order2",
        "detail": "Fisteku"
    }
]

Da Sie den genauen Deserialisierungsprozess nicht steuern (RestEasy), besteht eine erste Option darin, den JSON einfach als zu injizieren Stringund dann die Kontrolle über den Deserialisierungsprozess zu übernehmen:

Collection<COrder> readValues = new ObjectMapper().readValue(
    jsonAsString, new TypeReference<Collection<COrder>>() { }
);

Sie würden ein wenig an Bequemlichkeit verlieren, dies nicht selbst tun zu müssen, aber Sie würden das Problem leicht lösen.

Eine andere Option - wenn Sie den JSON nicht ändern können - besteht darin, einen Wrapper zu erstellen, der zur Struktur Ihrer JSON-Eingabe passt - und diesen stattdessen zu verwenden Collection<COrder>.

Hoffe das hilft.

Eugen
quelle
1
Großartig, ich habe in die Sammlung eingewickelt, weil es ein Beispiel in Resteasy-Dokumenten gab, aber es war mit XML.
Isah
2
@ Isah schön, können Sie diesen Resteasy-Link hier bitte
teilen
wenn ich darüber nachdenke. Ich bin mir ziemlich sicher, dass ich den richtigen Code habe und dies stundenlang debugge und meine Codes auf dem Weg ändere. Es stellte sich heraus, dass mir nur die eckigen Klammern fehlten, um anzuzeigen, dass mein Beitrag ein Array ist. Nun, ich denke, das ist ein Fluch für Neulinge, LOL. Folgen Sie meinen Schritten nicht, wenn Sie neu bei JSON und Spring Data sind. :(
iamjoshua
Der Code funktioniert bei mir nicht. In der Steuerung, was sollte der erwartete Code sein?
prem30488
62

Anstelle des JSON-Dokuments können Sie das ObjectMapper-Objekt wie folgt aktualisieren:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Salah Atwa
quelle
1
Danke, die Antwort hat mir geholfen
Jesús Sánchez
Fantastisch! Du hast einen Tag gerettet.
Herve Mutombo
danke, bitte besuchen Sie unsere Website mboot.herokuapp.com , wir veröffentlichen Artikel im Zusammenhang mit Java - Spring-Boot
Salah Atwa
8

Das wird funktionieren:

Das Problem kann auftreten, wenn Sie versuchen, eine Liste mit einem einzelnen Element als JsonArray und nicht als JsonNode zu lesen oder umgekehrt.

Da Sie nicht sicher wissen können, ob die zurückgegebene Liste ein einzelnes Element (der JSON sieht also so aus {...} ) oder mehrere Elemente (und der JSON so aussieht [{...}, {... }] ) - Sie müssen zur Laufzeit den Typ des Elements überprüfen.

Es sollte so aussehen:

(Hinweis: In diesem Codebeispiel verwende ich com.fasterxml.jackson.)

String jsonStr = response.readEntity(String.class);
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonStr);

// Start by checking if this is a list -> the order is important here:                      
if (rootNode instanceof ArrayNode) {
    // Read the json as a list:
    myObjClass[] objects = mapper.readValue(rootNode.toString(), myObjClass[].class);
    ...
} else if (rootNode instanceof JsonNode) {
    // Read the json as a single object:
    myObjClass object = mapper.readValue(rootNode.toString(), myObjClass.class);
    ...
} else {
    ...
}
Naor Bar
quelle
7

In Bezug auf Eugens Antwort können Sie diesen speziellen Fall lösen, indem Sie ein Wrapper-POJO-Objekt erstellen, das eine Collection<COrder>als Mitgliedsvariable enthält . Dies wird Jackson richtig führen, um das tatsächliche zu platzierenCollection Daten in die Mitgliedsvariable des POJO zu platzieren und den JSON zu erstellen, nach dem Sie in der API-Anforderung suchen.

Beispiel:

public class ApiRequest {

   @JsonProperty("collection")
   private Collection<COrder> collection;

   // getters
}

Stellen Sie dann den Parametertyp COrderRestService.postOrder()als Ihren neuen ApiRequestWrapper POJO anstelle von ein Collection<COrder>.

Adil B.
quelle
2

Ich bin heutzutage auf dasselbe Problem gestoßen, und vielleicht könnten weitere Details für jemand anderen hilfreich sein.

Ich habe einige Sicherheitsrichtlinien für REST-APIs gesucht und ein sehr interessantes Problem festgestellt mit JSON-Arrays festgestellt. Überprüfen Sie den Link auf Details, aber im Grunde sollten Sie sie in ein Objekt einschließen, wie wir bereits in dieser Beitragsfrage gesehen haben.

Also statt:

  [
    {
      "name": "order1"
    },
    {
      "name": "order2"
    }
  ]

Es ist ratsam, dass wir immer tun:

  {
    "data": [
      {
        "name": "order1"
      },
      {
        "name": "order2"
      }
    ]
  }

Dies ist ziemlich einfach, wenn Sie ein GET durchführen , kann Ihnen jedoch Probleme bereiten, wenn Sie stattdessen versuchen, denselben JSON zu POSTEN / SETZEN.

In meinem Fall hatte ich mehr als ein GET , das eine Liste war, und mehr als ein POST / PUT , das den gleichen JSON erhalten würde.

Am Ende habe ich also ein sehr einfaches Wrapper- Objekt für eine Liste verwendet :

public class Wrapper<T> {
  private List<T> data;

  public Wrapper() {}

  public Wrapper(List<T> data) {
    this.data = data;
  }
  public List<T> getData() {
    return data;
  }
  public void setData(List<T> data) {
    this.data = data;
  }
}

Die Serialisierung meiner Listen erfolgte mit einem @ControllerAdvice :

@ControllerAdvice
public class JSONResponseWrapper implements ResponseBodyAdvice<Object> {

  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return true;
  }

  @Override
  @SuppressWarnings("unchecked")
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if (body instanceof List) {
      return new Wrapper<>((List<Object>) body);
    }
    else if (body instanceof Map) {
      return Collections.singletonMap("data", body);
    }  
    return body;
  }
}

Alle Listen und Karten wurden wie folgt über ein Datenobjekt gewickelt :

  {
    "data": [
      {...}
    ]
  }

Die Deserialisierung war immer noch Standard, nur mit de Wrapper Object:

@PostMapping("/resource")
public ResponseEntity<Void> setResources(@RequestBody Wrapper<ResourceDTO> wrappedResources) {
  List<ResourceDTO> resources = wrappedResources.getData();
  // your code here
  return ResponseEntity
           .ok()
           .build();
}

Das war's! Hoffe es hilft jemandem.

Hinweis: getestet mit SpringBoot 1.5.5.RELEASE .

Reginaldo Santos
quelle
1
Die Wrapper-Klasse ist echt
Omid Rostami
0

Ich hatte dieses Problem mit einer REST-API, die mit dem Spring-Framework erstellt wurde. Durch Hinzufügen einer @ ResponseBody-Annotation (um die Antwort JSON zu erstellen) wurde das Problem behoben.

Will tun
quelle
0

Normalerweise tritt dieses Problem auf, wenn beim Zuordnen des JSON-Knotens zum Java-Objekt ein Problem auftritt. Ich hatte das gleiche Problem, weil in der Prahlerei der Knoten als Typ-Array definiert war und das JSON-Objekt nur ein Element hatte, weshalb das System Schwierigkeiten hatte, eine Elementliste einem Array zuzuordnen.

In Swagger wurde das Element definiert als

Test:
 "type": "array",
 "minItems": 1,
 "items": {
   "$ref": "#/definitions/TestNew"
  }

Während es sein sollte

Test:
    "$ref": "#/definitions/TestNew"

Und TestNewsollte vom Typ Array sein

Ambuj Sinha
quelle
0
Dto response = softConvertValue(jsonData, Dto.class);


     public static <T> T softConvertValue(Object fromValue, Class<T> toValueType) 
        {
            ObjectMapper objMapper = new ObjectMapper();
            return objMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                    .convertValue(fromValue, toValueType);
        }
Dipen Chawla
quelle
0

Gleicher Fehler:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.UUID` out of START_OBJECT token

Was es verursachte, war das Folgende:

ResponseEntity<UUID> response = restTemplate.postForEntity("/example/", null, UUID.class);

In meinem Test habe ich die Anfrage absichtlich auf null gesetzt (kein Inhalts-POST). Wie bereits erwähnt, war die Ursache für das OP dieselbe, da die Anforderung keinen gültigen JSON enthielt, sodass sie nicht automatisch als Anwendungs- / JSON-Anforderung identifiziert werden konnte, was die Einschränkung auf dem Server darstellte ( consumes = "application/json"). Eine gültige JSON-Anfrage wäre. Was behoben wurde, war das explizite Auffüllen einer Entität mit Nullkörper- und JSON-Headern.

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity request = new HttpEntity<>(null, headers);
ResponseEntity<UUID> response = restTemplate.postForEntity("/example/", request, UUID.class);
Tanel
quelle
0

In meinem Fall wurde der Fehler angezeigt, da meine JSON-Datei beim Lesen meiner JSON-Datei mit der Jackson-Bibliothek nur 1 Objekt enthielt. Daher begann es mit "{" und endete mit "}". Aber während ich es las und in einer Variablen speicherte, speicherte ich es in einem Array-Objekt (wie in meinem Fall könnte es mehr als ein Objekt geben).

Daher habe ich "[" am Anfang und "]" am Ende meiner JSON-Datei hinzugefügt, um sie in ein Array von Objekten zu konvertieren, und es hat einwandfrei ohne Fehler funktioniert.

Akshay Chopra
quelle
0

Wie oben erwähnt, würde Folgendes das Problem lösen: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

In meinem Fall hat der Anbieter diese [0..1] oder [0 .. *] Serialisierung jedoch eher als Fehler durchgeführt, und ich konnte die Korrektur nicht erzwingen. Auf der anderen Seite wollte es meinen strengen Mapper nicht für alle anderen Fälle beeinflussen, die streng validiert werden müssen.

Also habe ich einen Jackson NASTY HACK gemacht (der im Allgemeinen nicht kopiert werden sollte ;-)), vor allem, weil mein SingleOrListElement nur wenige Eigenschaften zum Patchen hatte:

@JsonProperty(value = "SingleOrListElement", access = JsonProperty.Access.WRITE_ONLY)
private Object singleOrListElement; 

public List<SingleOrListElement> patch(Object singleOrListElement) {
  if (singleOrListElement instanceof List) {
    return (ArrayList<SingleOrListElement>) singleOrListElement;
  } else {
    LinkedHashMap map = (LinkedHashMap) singleOrListElement;
    return Collections.singletonList(SingletonList.builder()
                            .property1((String) map.get("p1"))
                            .property2((Integer) map.get("p2"))
                            .build());
  }
Phirzel
quelle
-1

@JsonFormat (mit = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) private List <Corder> -Bestellungen;

Suhas Saheer
quelle
Bitte geben Sie eine vollständige Antwort mit etwas Code in Codeblöcken und etwas Text, der die Richtigkeit Ihrer Antwort
bewertet