Tidak dapat menghilangkan derialisasi instance java.util.ArrayList dari token START_OBJECT

129

Saya mencoba untuk POST Listobjek kustom. JSON saya di badan permintaan adalah ini:

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

Kode sisi server yang menangani permintaan:

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();
    }
}

Entitas 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() + "]";
    }
}

Tapi ada pengecualian:

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
sumber

Jawaban:

156

Masalahnya adalah JSON - ini tidak dapat, secara default, dideserialisasi menjadi Collectionkarena ini sebenarnya bukan JSON Array - yang akan terlihat seperti ini:

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

Karena Anda tidak mengontrol proses deserialisasi yang tepat (RestEasy melakukannya) - opsi pertama adalah dengan menyuntikkan JSON sebagai a Stringdan kemudian mengendalikan proses deserialisasi:

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

Anda akan kehilangan sedikit kenyamanan karena tidak harus melakukannya sendiri, tetapi Anda akan dengan mudah menyelesaikan masalahnya.

Opsi lain - jika Anda tidak dapat mengubah JSON - akan membuat pembungkus agar sesuai dengan struktur masukan JSON Anda - dan menggunakannya sebagai ganti Collection<COrder>.

Semoga ini membantu.

Eugen
sumber
1
Bagus, saya membungkus koleksi karena ada contoh di dokumen Resteasy, tetapi dengan XML.
isah
2
@isah bagus, bisakah Anda membagikan tautan resteasy itu di sini
nanospeck
kalau dipikir-pikir. Saya cukup yakin bahwa saya memiliki kode yang tepat dan men-debug ini selama berjam-jam, dan mengubah kode saya di sepanjang jalan. Ternyata saya baru saja melewatkan tanda kurung siku untuk menunjukkan bahwa posting saya adalah sebuah array.Arrg. Yah saya rasa ini adalah kutukan pemula, LOL. Bagi siapa pun yang baru mengenal JSON dan Spring Data, jangan ikuti jejak saya. :(
iamjoshua
Kode tidak berfungsi untuk saya, di pengontrol apa yang harus menjadi kode yang diharapkan?
prem30488
63

Alih-alih dokumen JSON, Anda dapat memperbarui objek ObjectMapper seperti di bawah ini:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Salah Atwa
sumber
1
Terima kasih, jawabannya membantu saya
Jesús Sánchez
Fantastis! Anda menghemat satu hari.
Herve Mutombo
terima kasih, silakan merujuk ke situs kami mboot.herokuapp.com , kami menerbitkan artikel terkait java - sepatu bot musim semi
Salah Atwa
8

Ini akan berhasil:

Masalahnya mungkin terjadi saat Anda mencoba membaca daftar dengan satu elemen sebagai JsonArray daripada JsonNode atau sebaliknya.

Karena Anda tidak dapat mengetahui dengan pasti apakah daftar yang dikembalikan berisi satu elemen (jadi json terlihat seperti ini {...} ) atau beberapa elemen (dan json terlihat seperti ini [{...}, {... }] ) - Anda harus memeriksa dalam runtime jenis elemen.

Ini akan terlihat seperti ini:

(Catatan: dalam contoh kode ini saya menggunakan 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
sumber
7

Terkait dengan jawaban Eugen, Anda dapat menyelesaikan kasus khusus ini dengan membuat objek POJO pembungkus yang berisi Collection<COrder>variabel anggotanya. Ini akan memandu Jackson dengan benar untuk menempatkan Collectiondata aktual di dalam variabel anggota POJO dan menghasilkan JSON yang Anda cari dalam permintaan API.

Contoh:

public class ApiRequest {

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

   // getters
}

Kemudian atur jenis parameter COrderRestService.postOrder()menjadi ApiRequestPOJO pembungkus baru Anda, bukan Collection<COrder>.

Adil B
sumber
2

Saya mengalami masalah yang sama akhir-akhir ini dan mungkin beberapa detail lebih lanjut mungkin dapat membantu orang lain.

Saya sedang mencari beberapa pedoman keamanan untuk REST API dan mengatasi masalah yang sangat menarik dengan json array. Periksa tautan untuk detailnya, tetapi pada dasarnya, Anda harus membungkusnya di dalam Objek seperti yang telah kita lihat di pertanyaan posting ini.

Jadi, alih-alih:

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

Sangat disarankan agar kami selalu melakukan:

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

Ini sangat mudah ketika Anda melakukan GET , tetapi mungkin memberi Anda masalah jika, sebaliknya, Anda mencoba POST / PUT json yang sama.

Dalam kasus saya, saya memiliki lebih dari satu GET yang merupakan Daftar dan lebih dari satu POST / PUT yang akan menerima json yang sama.

Jadi yang akhirnya saya lakukan adalah menggunakan objek Wrapper yang sangat sederhana untuk List :

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;
  }
}

Serialisasi daftar saya dibuat dengan @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;
  }
}

Jadi, semua Daftar dan Peta yang dibungkus di atas objek data seperti di bawah ini:

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

Deserialization masih default, hanya menggunakan 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();
}

Itu dia! Semoga membantu seseorang.

Catatan: diuji dengan SpringBoot 1.5.5.RELEASE .

Reginaldo Santos
sumber
1
kelas pembungkus asli
Omid Rostami
0

Saya mengalami masalah ini di REST API yang dibuat menggunakan framework Spring. Menambahkan anotasi @ResponseBody (untuk membuat tanggapan JSON) menyelesaikannya.

Do Will
sumber
0

Biasanya kita menghadapi masalah ini ketika ada masalah dalam memetakan node JSON dengan objek Java. Saya menghadapi masalah yang sama karena dalam kesombongan node didefinisikan sebagai Type array dan objek JSON hanya memiliki satu elemen, oleh karena itu sistem mengalami kesulitan dalam memetakan satu daftar elemen ke array.

Di Swagger, elemen didefinisikan sebagai

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

Padahal seharusnya

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

Dan TestNewharus bertipe array

Ambuj Sinha
sumber
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
sumber
0

Masalah yang sama:

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

Penyebabnya adalah sebagai berikut:

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

Dalam pengujian saya, saya sengaja menetapkan permintaan sebagai null (tanpa POST konten). Seperti yang disebutkan sebelumnya, penyebab OP sama karena permintaan tersebut tidak berisi JSON yang valid, sehingga tidak dapat secara otomatis diidentifikasi sebagai permintaan application / json yang merupakan batasan pada server ( consumes = "application/json"). Permintaan JSON yang valid adalah. Yang memperbaikinya adalah mengisi entitas dengan badan null dan header json secara eksplisit.

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
sumber
0

Dalam kasus saya, kesalahan ditampilkan karena ketika saya membaca file JSON saya menggunakan pustaka Jackson, file JSON saya hanya berisi 1 objek. Oleh karena itu, ini dimulai dengan "{" dan diakhiri dengan "}". Tetapi saat membaca dan menyimpannya dalam variabel, saya menyimpannya dalam objek Array (Seperti dalam kasus saya, mungkin ada lebih dari 1 objek).

Oleh karena itu, saya menambahkan "[" di awal dan "]" di akhir file JSON saya untuk mengubahnya menjadi array objek dan itu bekerja dengan baik tanpa kesalahan.

Akshay Chopra
sumber
0

Seperti disebutkan di atas, berikut ini akan menyelesaikan masalah: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Namun dalam kasus saya, penyedia melakukan serialisasi [0..1] atau [0 .. *] ini sebagai bug dan saya tidak dapat memaksakan perbaikan. Di sisi lain, itu tidak ingin memengaruhi pembuat peta ketat saya untuk semua kasus lain yang perlu divalidasi secara ketat.

Jadi saya melakukan Jackson NASTY HACK (yang tidak boleh disalin secara umum ;-)), terutama karena SingleOrListElement saya hanya memiliki beberapa properti untuk ditambal:

@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
sumber
-1

@JsonFormat (with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) private List <COrder> orders;

Suhas Saheer
sumber
Berikan jawaban lengkap dengan beberapa kode di dalam blok kode dan beberapa teks yang mengevaluasi kebenaran jawaban Anda
Ioannis Barakos