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.