START_OBJECTトークン外のjava.util.ArrayListのインスタンスをデシリアライズできません


129

ListカスタムオブジェクトをPOSTしようとしています。リクエスト本文の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"
    }
]

逆シリアル化の正確なプロセスを制御していないので(RestEasyはそうします)- 最初のオプションは、JSONを単にとして挿入してStringから、逆シリアル化プロセスを制御することです。

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

自分で行う必要がないという便利さは少し失われますが、問題は簡単に解決できます。

別のオプション( JSONを変更できない場合)は、JSON入力の構造に合うようにラッパーを作成し、の代わりにそれを使用することですCollection<COrder>

お役に立てれば。


1
すばらしい、Resteasyのドキュメントに例があったのでコレクションにラップしましたが、それはXMLを使用したものでした。
isah

2
@isahよろしい、そのRESTEASYリンクをここで共有できますか
nanospeck

良く考えると。適切なコードがあり、これを何時間もデバッグし、途中でコードを変更していることは間違いありません。私の投稿がarray.Arrgであることを示すために角括弧が欠けていたことがわかりました。まあ、これは初心者の呪いだと思います、LOL。JSONとSpring Dataの初心者には、私の足跡をたどらないでください。:(
iamjoshua

コードが私のために機能していません、コントローラーでは期待されるコードは何ですか?
prem30488

62

JSONドキュメントの代わりに、以下のようにObjectMapperオブジェクトを更新できます。

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

1
ありがとう、答えは私を助けた
ヘスス・サンチェス

素晴らしい!1日を節約しました。
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の回答に関連しCollection<COrder>て、メンバー変数としてを含むラッパーPOJOオブジェクトを作成することにより、この特定のケースを解決できます。これにより、JacksonがCollectionPOJOのメンバー変数内に実際のデータを配置し、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しようとすると、問題が発生する可能性があります。

私の場合、私は、複数の持っていたGETしたリストと、複数のPOST / PUT非常に同じJSONを受け取ることになります。

だから私がやったことは、非常に単純なWrapperオブジェクトを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;
  }
}

リストのシリアル化は@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": [
      {...}
    ]
  }

Wrapperオブジェクトを使用するだけで、デシリアライゼーションはデフォルトのままでした。

@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オブジェクトには要素が1つしかなかったため、同じ問題に直面しました。そのため、システムは1つの要素リストを配列にマッピングすることが困難でした。

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の原因は同じであり、サーバーの制限であるapplication / jsonリクエストとして自動的に識別できませんでした(consumes = "application/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ファイルには1つのオブジェクトしか含まれていなかったため、エラーが表示されていました。したがって、「{」で始まり、「}」で終わりました。しかし、それを読み取って変数に格納するとき、私はArrayオブジェクトに格納していました(私の場合のように、複数のオブジェクトが存在する可能性があります)。

したがって、JSONファイルの最初に「[」を追加し、最後に「]」を追加してオブジェクトの配列に変換すると、エラーなしで問題なく動作しました。


0

上記のとおり、次の方法で問題を解決できます。 mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

ただし、私の場合、プロバイダーはこの[0..1]または[0 .. *]のシリアライゼーションをバグとして行ったため、修正を強制できませんでした。一方、厳密に検証する必要がある他のすべてのケースでは、私の厳密なマッパーに影響を与えたくありませんでした。

したがって、特にSingleOrListElementにパッチを適用するプロパティがほとんどないため、Jackson NASTY HACKを実行しました(これは一般にコピーしないでください;-))。

@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)private List <COrder>注文;


コードブロック内のいくつかのコードとあなたの答えの正しさを評価するいくつかのテキストで完全な答えを提供してください
Ioannis Barakos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.