START_OBJECT 토큰에서 java.util.ArrayList의 인스턴스를 역 직렬화 할 수 없습니다.


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입니다. 이것은 기본적 Collection으로 JSON 배열 이 아니기 때문에 기본적으로 역 직렬화 할 수 없습니다 .

[
    {
        "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을 변경할 수없는 경우)은 JSON 입력 구조에 맞게 래퍼를 구성하고 대신 Collection<COrder>.

도움이 되었기를 바랍니다.


1
좋아요, Resteasy 문서에 예제가 있었기 때문에 컬렉션으로 포장했지만 XML을 사용했습니다.
— isah dec

2
@isah 멋지네요, 여기 resteasy 링크를 공유해 주 시겠어요?
— nanospeck apr

그것을 생각하게됩니다. 나는 내가 올바른 코드를 가지고 있고 이것을 몇 시간 동안 디버깅하고 그 과정에서 코드를 변경한다고 확신합니다. 내 게시물이 배열임을 나타 내기 위해 대괄호가 누락 된 것으로 나타났습니다. 글쎄요, 이건 초보자들의 저주라고 생각 해요, LOL. JSON 및 SpringData를 처음 접하는 사람에게는 내 발자취를 따르지 마십시오. :(
— iamjoshua

코드가 나를 위해 작동하지 않습니다. 컨트롤러에서 예상되는 코드는 무엇입니까?
— prem30488

63

JSON 문서 대신 아래와 같이 ObjectMapper 객체를 업데이트 할 수 있습니다.

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

1
감사합니다. 답변이 도움이되었습니다
— Jesús Sánchez

환상적입니다! 당신은 하루를 구했습니다.
— Herve Mutombo

감사합니다. mboot.herokuapp.com 사이트를 참조하십시오. java-spring-boot
— Salah Atwa

8

이것은 작동합니다.

이 문제는 단일 요소가있는 목록을 JsonNode 가 아닌 JsonArray로 읽 거나 그 반대로 읽으려고 할 때 발생할 수 있습니다 .

반환 된 목록에 단일 요소가 포함되어 있는지 (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의 답변과 관련하여를 Collection<COrder>멤버 변수로 포함하는 래퍼 POJO 개체를 만들어이 특정 경우를 해결할 수 있습니다 . 그러면 Jackson Collection이 POJO의 멤버 변수에 실제 데이터 를 배치 하고 API 요청에서 찾고있는 JSON을 생성하도록 올바르게 안내 할 것 입니다.

예:

public class ApiRequest {

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

   // getters
}

그런 다음의 매개 변수 유형을 대신 COrderRestService.postOrder()새 ApiRequest랩퍼 POJO로 설정하십시오 Collection<COrder>.


2

나는 요즘이 같은 문제에 부딪 혔고 아마도 더 많은 세부 사항이 다른 사람에게 도움이 될 수 있습니다.

REST API에 대한 몇 가지 보안 지침을 찾고 있었고 json 배열과 관련 하여 매우 흥미로운 문제 를 겪었습니다. 자세한 내용은 링크를 확인하십시오. 그러나 기본적으로이 게시물 질문에서 이미 살펴본 것처럼 객체 내에서 래핑해야합니다.

따라서 대신 :

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

항상 다음과 같이하는 것이 좋습니다.

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

이것은 GET을 할 때 매우 간단 하지만, 대신 동일한 json 을 POST / PUT 하려고하면 문제가 발생할 수 있습니다 .

제 경우 에는 List 인 GET 이 두 개 이상이고 동일한 json을 수신하는 POST / PUT 이 두 개 이상 있습니다.

그래서 내가 한 일은 List에 매우 간단한 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

Spring 프레임 워크를 사용하여 만든 REST API에서이 문제가 발생했습니다. @ResponseBody 주석을 추가하여 (응답 JSON을 만들기 위해) 해결했습니다.


0

일반적으로 JSON 노드를 Java 객체의 노드와 매핑하는 데 문제가있을 때이 문제에 직면합니다. 나는 swagger에서 노드가 Type 배열로 정의되고 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);

내 테스트에서 의도적으로 요청을 null (콘텐츠 POST 없음)으로 설정했습니다. 앞서 언급했듯이 요청에 유효한 JSON이 포함되지 않았기 때문에 OP의 원인이 동일했기 때문에 서버 ( consumes = "application/json") 의 제한이었던 애플리케이션 / json 요청으로 자동 식별되지 않았습니다 . 유효한 JSON 요청은 다음과 같습니다. 수정 된 것은 명시 적으로 null 본문과 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

제 경우에는 Jackson 라이브러리를 사용하여 JSON 파일을 읽을 때 JSON 파일에 하나의 개체 만 포함되어 있기 때문에 오류가 표시되었습니다. 따라서 "{"로 시작하고 "}"로 끝납니다. 그러나 그것을 읽고 변수에 저장하는 동안 나는 그것을 Array 객체에 저장했습니다 (제 경우에는 하나 이상의 객체가있을 수 있습니다).

따라서 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 (with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) 개인 목록 <COrder> 주문;


일부 코드 내부의 코드 블록을 가진 완전한 답변과 답변의 정확성을 평가하는 일부 텍스트를 입력하십시오
— 요안 Barakos
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.