ไม่สามารถยกเลิกการกำหนดค่าอินสแตนซ์ของ java.util.ArrayList ออกจากโทเค็น START_OBJECT


129

ฉันกำลังพยายามโพสต์Listวัตถุที่กำหนดเอง JSON ของฉันในเนื้อหาคำขอคือ:

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

รหัสฝั่งเซิร์ฟเวอร์ที่จัดการคำขอ:

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

เอนทิตี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() + "]";
    }
}

แต่มีข้อยกเว้นเกิดขึ้น:

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)

คำตอบ:


156

ปัญหาคือ JSON ซึ่งโดยค่าเริ่มต้นจะไม่สามารถกำหนดค่าเริ่มต้นเป็น a ได้Collectionเนื่องจากไม่ใช่ JSON Array ซึ่งจะมีลักษณะดังนี้:

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

เนื่องจากคุณไม่ได้ควบคุมกระบวนการ deserialization ที่แน่นอน (RestEasy ทำ) - ตัวเลือกแรกคือเพียงแค่ฉีด JSON เป็น a Stringจากนั้นเข้าควบคุมกระบวนการ deserialization:

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

คุณจะได้รับความสะดวกสบายเล็กน้อยโดยไม่ต้องทำสิ่งนั้นด้วยตัวเอง แต่คุณสามารถจัดการปัญหาได้อย่างง่ายดาย

อีกทางเลือกหนึ่ง - หากคุณไม่สามารถเปลี่ยน JSON ได้ก็คือการสร้าง wrapper ให้พอดีกับโครงสร้างของอินพุต JSON ของคุณและใช้แทนCollection<COrder>และการใช้งานที่แทน

หวังว่านี่จะช่วยได้


1
เยี่ยมมากฉันรวบรวมคอลเลคชันไว้เพราะมีตัวอย่างใน Resteasy docs แต่ใช้กับ XML
isah

2
@isah ดีคุณช่วยแชร์ลิงค์ resteasy ที่นี่ได้
ไหม

ลองคิดดูสิ ฉันค่อนข้างมั่นใจว่าฉันมีรหัสที่ถูกต้องและแก้ไขข้อบกพร่องนี้เป็นเวลาหลายชั่วโมงและเปลี่ยนรหัสไปพร้อมกัน ปรากฎว่าฉันขาดเพียงแค่วงเล็บเหลี่ยมเพื่อระบุว่าโพสต์ของฉันเป็นอาร์เรย์ Arrg ฉันเดาว่านี่เป็นคำสาปมือใหม่ฮ่า ๆ สำหรับทุกคนที่เพิ่งเริ่มใช้ JSON และ Spring Data อย่าทำตามรอยเท้าของฉัน :(
iamjoshua

รหัสไม่ทำงานสำหรับฉันในตัวควบคุมรหัสที่คาดไว้ควรเป็นอย่างไร
prem30488

63

แทนที่จะเป็นเอกสาร JSON คุณสามารถอัปเดตวัตถุ ObjectMapper ดังต่อไปนี้:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

1
ขอบคุณคำตอบช่วยฉัน
JesúsSánchez

Fantastic! คุณช่วยชีวิตได้หนึ่งวัน
Herve Mutombo

ขอบคุณโปรดดูที่เว็บไซต์ mboot.herokuapp.com ของเราเราเผยแพร่บทความที่เกี่ยวข้องกับ java - spring-boot
Salah Atwa

8

สิ่งนี้จะได้ผล:

ปัญหาอาจเกิดขึ้นเมื่อคุณพยายามอ่านรายการที่มีองค์ประกอบเดียวเป็นJsonArrayแทนที่จะเป็นJsonNodeหรือในทางกลับกัน

เนื่องจากคุณไม่สามารถทราบได้อย่างแน่นอนว่ารายการที่ส่งคืนมีองค์ประกอบเดียว(ดังนั้น json จึงมีลักษณะเช่นนี้{... } )หรือหลายองค์ประกอบ(และ json มีลักษณะดังนี้[{... }, {... }] ) - คุณจะต้องตรวจสอบประเภทขององค์ประกอบในรันไทม์

ควรมีลักษณะดังนี้:

(หมายเหตุ: ในตัวอย่างโค้ดนี้ฉันใช้ 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 {
    ...
}

7

ที่เกี่ยวข้องกับคำตอบของ Eugen คุณสามารถแก้ปัญหานี้ได้โดยการสร้างวัตถุ Wrapper POJO ที่มี a Collection<COrder>เป็นตัวแปรสมาชิก สิ่งนี้จะแนะนำให้แจ็คสันวางของจริงได้อย่างถูกต้องCollectionข้อมูลภายในตัวแปรสมาชิกของ POJOและสร้าง JSON ที่คุณต้องการในคำขอ API

ตัวอย่าง:

public class ApiRequest {

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

   // getters
}

จากนั้นตั้งค่าพารามิเตอร์ชนิดของCOrderRestService.postOrder()ที่จะเป็นใหม่ของคุณ ApiRequestเสื้อคลุม POJO Collection<COrder>แทน


2

วันนี้ฉันประสบปัญหาเดียวกันนี้และบางทีรายละเอียดเพิ่มเติมอาจเป็นประโยชน์กับคนอื่น

ฉันกำลังมองหาแนวทางการรักษาความปลอดภัยสำหรับ REST API และข้ามปัญหาที่น่าสนใจมากกับอาร์เรย์ json ตรวจสอบลิงค์เพื่อดูรายละเอียด แต่โดยพื้นฐานแล้วคุณควรรวมไว้ใน Object ตามที่เราเห็นในคำถามโพสต์นี้

ดังนั้นแทนที่จะ:

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

ขอแนะนำให้เราทำเสมอ:

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

สิ่งนี้ค่อนข้างตรงไปตรงมาเมื่อคุณทำGETแต่อาจทำให้คุณมีปัญหาได้หากคุณพยายามPOST / PUTเหมือนกันกับ json

ในกรณีของฉันฉันมีGETมากกว่าหนึ่งรายการที่เป็นรายการและมากกว่าหนึ่งPOST / PUTที่จะได้รับ json เดียวกัน

ดังนั้นสิ่งที่ฉันทำคือใช้วัตถุWrapper ที่เรียบง่ายมากสำหรับรายการ :

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

การจัดลำดับรายการของฉันทำด้วย@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;
  }
}

ดังนั้นรายการและแผนที่ทั้งหมดที่รวมอยู่บนวัตถุข้อมูลดังต่อไปนี้:

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

Deserialization ยังคงเป็นค่าเริ่มต้นเพียงแค่ใช้ 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();
}

นั่นมัน! หวังว่ามันจะช่วยใครบางคน

หมายเหตุ:การทดสอบกับ SpringBoot 1.5.5.RELEASE


1
คลาสกระดาษห่อเป็นของแท้
Omid Rostami

0

ฉันมีปัญหานี้ใน REST API ที่สร้างโดยใช้ Spring framework การเพิ่มคำอธิบายประกอบ @ResponseBody (เพื่อให้การตอบสนอง JSON) แก้ไขได้


0

โดยปกติเราประสบปัญหานี้เมื่อมีปัญหาในการแมปโหนด JSON กับวัตถุ Java ฉันประสบปัญหาเดียวกันเนื่องจากใน swagger โหนดถูกกำหนดให้เป็น Type array และออบเจ็กต์ JSON มีองค์ประกอบเดียวเท่านั้นดังนั้นระบบจึงมีปัญหาในการแมปรายการองค์ประกอบหนึ่งกับอาร์เรย์

ใน Swagger องค์ประกอบถูกกำหนดให้เป็น

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

ในขณะที่ควรจะเป็น

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

และTestNewควรเป็นประเภทอาร์เรย์


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

0

ปัญหาเดียวกัน:

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

สาเหตุเกิดจากอะไรดังต่อไปนี้:

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

ในการทดสอบของฉันฉันตั้งใจตั้งค่าคำขอเป็นโมฆะ (ไม่มีเนื้อหา POST) ตามที่กล่าวไว้ก่อนหน้านี้สาเหตุของ OP นั้นเหมือนกันเนื่องจากคำขอไม่มี JSON ที่ถูกต้องดังนั้นจึงไม่สามารถระบุได้โดยอัตโนมัติว่าเป็นคำขอ application / json ซึ่งเป็นข้อ จำกัด บนเซิร์ฟเวอร์ ( consumes = "application/json") คำขอ JSON ที่ถูกต้องจะเป็น สิ่งที่แก้ไขได้คือการเติมเอนทิตีที่มีเนื้อหาว่างและส่วนหัว json อย่างชัดเจน

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

0

ในกรณีของฉันข้อผิดพลาดกำลังแสดงขึ้นเนื่องจากเมื่อฉันอ่านไฟล์ JSON โดยใช้ไลบรารี Jackson ไฟล์ JSON ของฉันมีเพียง 1 วัตถุ ดังนั้นจึงเริ่มต้นด้วย "{" และลงท้ายด้วย "}" แต่ในขณะที่อ่านและเก็บไว้ในตัวแปรฉันเก็บไว้ในวัตถุ Array (เช่นในกรณีของฉันอาจมีมากกว่า 1 วัตถุ)

ดังนั้นฉันจึงเพิ่ม "[" ในการเริ่มต้นและ "]" ในตอนท้ายของไฟล์ JSON ของฉันเพื่อแปลงเป็นอาร์เรย์ของวัตถุและทำงานได้ดีอย่างสมบูรณ์โดยไม่มีข้อผิดพลาดใด ๆ


0

ดังที่ได้กล่าวไว้ข้างต้นสิ่งต่อไปนี้จะช่วยแก้ปัญหาได้: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

อย่างไรก็ตามในกรณีของฉันผู้ให้บริการทำสิ่งนี้ [0..1] หรือ [0 .. *] การทำให้เป็นอนุกรมแทนที่จะเป็นข้อบกพร่องและฉันไม่สามารถบังคับให้แก้ไขได้ ในทางกลับกันมันไม่ต้องการส่งผลกระทบต่อผู้ทำแผนที่ที่เข้มงวดของฉันสำหรับกรณีอื่น ๆ ทั้งหมดซึ่งจำเป็นต้องได้รับการตรวจสอบอย่างเคร่งครัด

ดังนั้นฉันจึงทำ Jackson NASTY HACK (ซึ่งไม่ควรคัดลอกโดยทั่วไป ;-)) โดยเฉพาะอย่างยิ่งเนื่องจาก SingleOrListElement ของฉันมีคุณสมบัติเพียงเล็กน้อยในการแก้ไข:

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

-1

@JsonFormat (พร้อม = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) รายการส่วนตัวคำสั่ง <COrder>;


โปรดให้คำตอบที่สมบูรณ์พร้อมรหัสบางส่วนในบล็อกโค้ดและข้อความบางส่วนที่ประเมินความถูกต้องของคำตอบของคุณ
Ioannis Barakos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.