START_OBJECT jetonu dışında java.util.ArrayList örneği seri durumdan çıkarılamıyor


129

Bir Listözel nesneyi YAYINLAMAYA çalışıyorum . İstek gövdesindeki JSON'um şudur:

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

İsteği işleyen sunucu tarafı kodu:

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

Varlık 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() + "]";
    }
}

Ancak bir istisna var:

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)

Yanıtlar:


156

Sorun JSON'dur - bu, varsayılan olarak serileştirilemez hale getirilemez Collectionçünkü bu aslında bir JSON Dizisi değildir - bu şöyle görünür:

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

Tam olarak serileştirme sürecini kontrol etmediğiniz için (RestEasy yapar) - ilk seçenek JSON'u basitçe bir olarak enjekte etmek Stringve ardından seriyi kaldırma işleminin kontrolünü almak olacaktır:

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

Bunu kendiniz yapmak zorunda olmamanın rahatlığını biraz kaybedersiniz, ancak sorunu kolayca çözersiniz.

Başka bir seçenek - JSON'u değiştiremezseniz - JSON girişinizin yapısına uyacak bir sarmalayıcı oluşturmak ve bunun yerine onu kullanmak olacaktır.Collection<COrder> .

Bu yardımcı olur umarım.


1
Harika, koleksiyona sardım çünkü Resteasy belgelerinde bir örnek vardı, ama XML ile oldu.
isah

2
güzel @isah bunu buradan resteasy bağlantısını paylaşabilir lütfen
nanospeck

Bunu bir düşün. Doğru koda sahip olduğumdan ve bunu saatlerce hata ayıkladığımdan ve bu sırada kodlarımı değiştirdiğimden oldukça eminim. Gönderimin bir dizi olduğunu belirtmek için köşeli parantezleri kaçırdığım ortaya çıktı. Sanırım bu yeni başlayanlar için bir lanet, LOL. JSON ve Spring Data'ya yeni başlayanlar için benim ayak izlerimi takip etme. :(
iamjoshua

Kod benim için çalışmıyor, denetleyicide beklenen kod ne olmalı?
prem30488

63

JSON belgesi yerine ObjectMapper nesnesini aşağıdaki gibi güncelleyebilirsiniz:

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

1
Teşekkür ederim, cevap bana yardımcı oldu
Jesús Sánchez

Fantastik! Bir gün kurtardın.
Herve Mutombo

teşekkürler, lütfen sitemize bakın mboot.herokuapp.com , java - spring-boot
Salah

8

Bu çalışacak:

Sorun, tek öğeli bir listeyi JsonNode yerine JsonArray olarak okumaya çalışırken veya tam tersi olabilir.

Döndürülen listenin tek bir öğe (yani json şuna benzer {...} ) veya birden çok öğe (ve json şuna benzer [{...}, {... }] ) - çalışma zamanında öğenin türünü kontrol etmeniz gerekir.

Şöyle görünmeli:

(Not: Bu kod örneğinde com.fasterxml.jackson kullanıyorum)

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'in cevabına bağlı olarak, bu durumu Collection<COrder>, üye değişkeni olarak a içeren bir sarmalayıcı POJO nesnesi oluşturarak çözebilirsiniz . Bu, Jackson'a gerçekCollection verileri POJO'nun üye değişkeninin içine yerleştirmesi ve API isteğinde aradığınız JSON'u üretmesi için .

Misal:

public class ApiRequest {

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

   // getters
}

Daha sonra parametre türünü, yerine COrderRestService.postOrder()yeni ApiRequestsarıcı POJO'nuz olacak şekilde ayarlayın Collection<COrder>.


2

Bugünlerde aynı problemle karşılaştım ve belki biraz daha detay başka biri için yardımcı olabilir.

REST API'leri için bazı güvenlik yönergeleri arıyordum ve json dizileriyle ilgili çok ilgi çekici bir sorunu aştım. Ayrıntılar için bağlantıyı kontrol edin, ancak temel olarak, bu yazı sorusunda daha önce gördüğümüz gibi, bunları bir Nesne içine sarmalısınız.

Yani, bunun yerine:

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

Her zaman şunları yapmamız tavsiye edilir:

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

Bir GET yaparken bu oldukça basittir , ancak bunun yerine aynı json'u POST / PUT yapmaya çalışıyorsanız size biraz sorun çıkarabilir .

Benim durumumda, Liste olan birden fazla GET ve aynı json'u alacak birden fazla POST / PUT vardı.

Sonunda yaptığım şey, bir Liste için çok basit bir Sarmalayıcı nesnesi kullanmaktı :

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

Listelerimin serileştirilmesi bir @ControllerAdvice ile yapıldı :

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

Dolayısıyla, aşağıdaki gibi bir veri nesnesinin üzerine sarılmış olan tüm Listeler ve Haritalar :

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

Seriyi kaldırma hala varsayılan olarak, sadece de Wrapper Object kullanılarak:

@PostMapping("/resource")
public ResponseEntity<Void> setResources(@RequestBody Wrapper<ResourceDTO> wrappedResources) {
  List<ResourceDTO> resources = wrappedResources.getData();
  // your code here
  return ResponseEntity
           .ok()
           .build();
}

İşte buydu! Umarım birine yardımcı olur.

Not: SpringBoot 1.5.5.RELEASE ile test edilmiştir .


1
sarıcı sınıfı gerçek
Omid Rostami

0

Bu sorunu Spring çerçevesi kullanılarak oluşturulmuş bir REST API'de yaşadım. @ResponseBody ek açıklaması eklemek (JSON yanıtını yapmak için) sorunu çözdü.


0

Normalde, JSON düğümünü Java nesnesiyle eşleştirmede bir sorun olduğunda bu sorunla karşılaşırız. Aynı sorunla karşılaştım çünkü swagger'da düğüm Type dizisi olarak tanımlandı ve JSON nesnesi yalnızca bir öğeye sahipti, bu nedenle sistem bir öğe listesini bir diziye eşlemekte zorluk çekiyordu.

Swagger'da öğe şu şekilde tanımlandı:

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

Olması gerektiği halde

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

Ve TestNewdizi türünde olmalıdır


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

Aynı sorun:

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

Buna sebep olan şuydu:

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

Testimde isteği bilerek boş olarak ayarladım (içerik POST yok). Daha önce belirtildiği gibi, OP'nin nedeni aynıydı çünkü istek geçerli bir JSON içermiyordu, bu nedenle sunucu ( consumes = "application/json") üzerindeki sınırlama olan bir uygulama / json isteği olarak otomatik olarak tanımlanamıyordu . Geçerli bir JSON isteği olacaktır. Düzeltilen şey, bir varlığı açıkça boş gövde ve json başlıklarıyla doldurmaktı.

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

Benim durumumda, JSON dosyamı Jackson kitaplığını kullanarak okurken JSON dosyam yalnızca 1 nesne içerdiğinden hata gösteriliyordu. Dolayısıyla "{" ile başladı ve "}" ile bitti. Ama onu okurken ve bir değişkende saklarken, onu bir Array nesnesinde saklıyordum (Benim durumumda olduğu gibi, birden fazla nesne olabilir).

Bu nedenle, JSON dosyamın başına "[" ve ​​sonuna "]" ekleyerek onu bir nesne dizisine dönüştürdüm ve hatasız mükemmel bir şekilde çalıştı.


0

Yukarıda belirtildiği gibi, aşağıdakiler sorunu çözecektir: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Ancak benim durumumda sağlayıcı bu [0..1] veya [0 .. *] serileştirmeyi bir hata olarak yaptı ve ben düzeltmeyi zorlayamadım. Öte yandan, sıkı bir şekilde doğrulanması gereken diğer tüm durumlar için katı eşleştiricimi etkilemek istemedi.

Bu yüzden bir Jackson NASTY HACK yaptım (genel olarak kopyalanmamalı ;-)), özellikle de SingleOrListElement'imin yamalanacak birkaç özelliği olduğu için:

@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) özel Liste <COrder> siparişleri;


Lütfen kod blokları içindeki bazı kodlarla ve cevabınızın doğruluğunu değerlendiren bazı metinlerle eksiksiz bir cevap verin
Ioannis Barakos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.