Không thể loại bỏ phiên bản java.util.ArrayList ra khỏi mã thông báo START_OB DỰ ÁN


129

Tôi đang cố gắng để gửi một Listđối tượng tùy chỉnh. JSON của tôi trong cơ thể yêu cầu là thế này:

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

Mã phía máy chủ xử lý yêu cầu:

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

Thực thể 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() + "]";
    }
}

Nhưng một ngoại lệ được đưa ra:

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)

Câu trả lời:


156

Vấn đề là JSON - theo mặc định, điều này không thể được giải thích thành một Collectionvì nó không thực sự là một mảng JSON - trông giống như thế này:

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

Vì bạn không kiểm soát chính xác quá trình khử lưu huỳnh (RestPal thực hiện) - nên một tùy chọn đầu tiên là chỉ cần tiêm JSON như một Stringvà sau đó kiểm soát quá trình khử lưu huỳnh:

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

Bạn sẽ mất một chút sự tiện lợi khi không phải tự mình làm điều đó, nhưng bạn sẽ dễ dàng giải quyết vấn đề.

Một tùy chọn khác - nếu bạn không thể thay đổi JSON - sẽ là xây dựng một trình bao bọc để phù hợp với cấu trúc của đầu vào JSON của bạn - và sử dụng nó thay vì Collection<COrder>.

Hi vọng điêu nay co ich.


1
Tuyệt vời, tôi gói trong bộ sưu tập vì có một ví dụ trong các tài liệu Resteasy, nhưng đó là với XML.
isah

2
@isah tốt đẹp, bạn có thể chia sẻ liên kết yên tĩnh đó ở đây không
nanospeck

Nghĩ kĩ thì. Tôi khá chắc chắn rằng tôi có mã đúng và gỡ lỗi này trong nhiều giờ và thay đổi mã của tôi trên đường đi. Hóa ra tôi chỉ thiếu dấu ngoặc vuông để cho biết bài viết của tôi là một mảng.Arrg. Chà tôi đoán đây là một lời nguyền của người mới, LOL. Đối với bất kỳ ai mới sử dụng JSON và Spring Data, đừng theo bước chân của tôi. :(
iamjoshua

Mã không hoạt động đối với tôi, trong trình điều khiển, mã dự kiến ​​sẽ là gì?
Prem30488

62

Thay vì tài liệu JSON, bạn có thể cập nhật đối tượng ObjectMapper như bên dưới:

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

1
Cảm ơn bạn, câu trả lời đã giúp tôi
Jesús Sánchez

Tuyệt diệu! Bạn đã lưu một ngày.
Herve Mutombo

cảm ơn, vui lòng tham khảo trang web của chúng tôi mboot.herokuapp.com , chúng tôi xuất bản bài viết liên quan đến java - spring-boot
Salah Atwa

8

Điều này sẽ làm việc:

Vấn đề có thể xảy ra khi bạn đang cố đọc một danh sách có một phần tử là JsonArray chứ không phải là JsonNode hoặc ngược lại.

Vì bạn không thể biết chắc rằng danh sách được trả về có chứa một phần tử không (vì vậy json trông giống như {...} ) hoặc nhiều phần tử (và json trông giống như thế này [{...}, {... }] ) - bạn sẽ phải kiểm tra thời gian chạy loại phần tử.

Nó sẽ giống như thế này:

(Lưu ý: trong mẫu mã này tôi đang sử dụng 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

Liên quan đến câu trả lời của Eugen, bạn có thể giải quyết trường hợp cụ thể này bằng cách tạo một đối tượng POJO bao bọc có chứa một Collection<COrder>biến thành viên của nó. Điều này sẽ hướng dẫn Jackson đúng cách để đặt thực tếCollection dữ liệu bên trong biến thành viên của POJO và tạo ra JSON mà bạn đang tìm kiếm trong yêu cầu API.

Thí dụ:

public class ApiRequest {

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

   // getters
}

Sau đó, đặt loại tham số COrderRestService.postOrder()ApiRequestPOJO của trình bao bọc mới thay vì Collection<COrder>.


2

Tôi đã gặp vấn đề tương tự những ngày này và có thể một số chi tiết có thể hữu ích cho người khác.

Tôi đã xem xét một số hướng dẫn bảo mật cho API REST và đã vượt qua một vấn đề rất hấp dẫn với mảng json. Kiểm tra liên kết để biết chi tiết, nhưng về cơ bản, bạn nên bọc chúng trong một Đối tượng như chúng ta đã thấy trong câu hỏi này.

Vì vậy, thay vì:

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

Chúng tôi khuyên bạn nên luôn luôn làm:

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

Điều này khá đơn giản khi bạn thực hiện GET , nhưng có thể gây cho bạn một số rắc rối nếu, thay vào đó, việc bạn cố gắng POST / PUT cùng một json.

Trong trường hợp của tôi, tôi đã có nhiều hơn một GETDanh sách và nhiều hơn một POST / PUT sẽ nhận được cùng một json.

Vì vậy, điều cuối cùng tôi làm là sử dụng một đối tượng Wrapper rất đơn giản cho Danh sách :

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

Việc xê-ri hóa danh sách của tôi đã được thực hiện với @ControllAdvice :

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

Vì vậy, tất cả các Danh sách và Bản đồ được bọc trong một đối tượng dữ liệu như dưới đây:

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

Deserialization vẫn được mặc định, chỉ cần sử dụng 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();
}

Điều đó là vậy đó! Hy vọng nó sẽ giúp được ai đó.

Lưu ý: đã thử nghiệm với SpringBoot 1.5.5.RELEASE .


1
lớp bao bọc là chính hãng
Omid Rostami

0

Tôi gặp vấn đề này trên API REST được tạo bằng khung Spring. Việc thêm chú thích @ResponseBody (để tạo JSON phản hồi) đã giải quyết nó.


0

Thông thường chúng ta phải đối mặt với vấn đề này khi có một vấn đề ánh xạ nút JSON với đối tượng Java. Tôi gặp phải vấn đề tương tự vì trong vênh vang, nút được định nghĩa là mảng Type và đối tượng JSON chỉ có một phần tử, do đó hệ thống gặp khó khăn trong việc ánh xạ một danh sách thành phần vào một mảng.

Trong Swagger, phần tử được định nghĩa là

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

Trong khi nó nên

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

TestNewnên là kiểu mảng


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

Cùng một vấn đề:

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

Điều gì gây ra nó là như sau:

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

Trong thử nghiệm của mình, tôi cố tình đặt yêu cầu là null (không có nội dung POST). Như đã đề cập trước đây, nguyên nhân của OP là như nhau vì yêu cầu không chứa JSON hợp lệ, do đó không thể tự động được xác định là yêu cầu ứng dụng / json, đó là giới hạn trên máy chủ ( consumes = "application/json"). Một yêu cầu JSON hợp lệ sẽ là. Điều cố định nó đã tạo ra một thực thể có tiêu đề cơ thể và tiêu đề json rõ ràng.

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

Trong trường hợp của tôi, lỗi đã được hiển thị vì khi tôi đọc tệp JSON của mình bằng thư viện Jackson, tệp JSON của tôi chỉ chứa 1 đối tượng. Do đó, nó bắt đầu bằng "{" và kết thúc bằng "}". Nhưng trong khi đọc nó và lưu trữ nó trong một biến, tôi đã lưu trữ nó trong một đối tượng Array (Như trong trường hợp của tôi, có thể có nhiều hơn 1 đối tượng).

Do đó, tôi đã thêm "[" vào đầu và "]" vào cuối tệp JSON của mình để chuyển đổi nó thành một mảng đối tượng và nó hoạt động hoàn toàn tốt mà không có bất kỳ lỗi nào.


0

Như đã đề cập ở trên, sau đây sẽ giải quyết vấn đề: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Tuy nhiên, trong trường hợp của tôi, nhà cung cấp đã thực hiện việc tuần tự hóa [0..1] hoặc [0 .. *] này như là một lỗi và tôi không thể thực thi sửa chữa. Mặt khác, nó không muốn tác động đến người lập bản đồ nghiêm ngặt của tôi cho tất cả các trường hợp khác cần được xác nhận nghiêm ngặt.

Vì vậy, tôi đã thực hiện một Jackson NASTY HACK (không nên sao chép chung ;-)), đặc biệt là vì SingleOrListEuity của tôi chỉ có một vài thuộc tính để vá:

@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 (with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) Danh sách riêng tư <COrder> đơn hàng;


Vui lòng cung cấp câu trả lời đầy đủ với một số mã bên trong các khối mã và một số văn bản đánh giá tính chính xác của câu trả lời của bạn
Ioannis Barakos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.