Search code examples
javajsonresttemplatespring-3

RestTemplate cannot deserialize a JSON array


When I try to get a list of values using RestTemplate, I get an error. I use Spring 3.0.0.

Caused by: org.springframework.web.client.RestClientException: Error while extracting response for type [class [Lru.sandwichcloud.Ingredient;] and content type [application/json] at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:118) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1132) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:1115) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:865) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:764) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:405) ~[spring-web-6.0.2.jar!/:6.0.2] at ru.sandwichcloud.SandwichCloudClient.getAllIngredients(SandwichCloudClient.java:42) ~[classes!/:0.0.7-SNAPSHOT] at ru.sandwichcloud.RestExamples.lambda$fetchIngredients$0(RestExamples.java:34) ~[classes!/:0.0.7-SNAPSHOT] at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:767) ~[spring-boot-3.0.0.jar!/:3.0.0] 11 common frames omitted Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type [Lru.sandwichcloud.Ingredient; from Object value (token JsonToken.START_OBJECT) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:406) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:354) ~[spring-web-6.0.2.jar!/:6.0.2] at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:103) ~[spring-web-6.0.2.jar!/:6.0.2] 19 common frames omitted Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type [Lru.sandwichcloud.Ingredient; from Object value (token JsonToken.START_OBJECT) at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 1] at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1746) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1520) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1467) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.handleNonArray(ObjectArrayDeserializer.java:345) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:196) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.deser.std.ObjectArrayDeserializer.deserialize(ObjectArrayDeserializer.java:26) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2105) ~[jackson-databind-2.14.1.jar!/:2.14.1] at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1481) ~[jackson-databind-2.14.1.jar!/:2.14.1] at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:395) ~[spring-web-6.0.2.jar!/:6.0.2] 21 common frames omitted

This is how JSON object looks like for "http://localhost:8080/data-api/ingredients":

  "_embedded" : {
    "ingredients" : [ {
      "name" : "Dark Bread",
      "type" : "BREAD",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/DARK"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/DARK"
        }
      }
    }, {
      "name" : "White Bread",
      "type" : "BREAD",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/WHTE"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/WHTE"
        }
      }
    }, {
      "name" : "Beef",
      "type" : "PROTEIN",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/BEEF"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/BEEF"
        }
      }
    }, {
      "name" : "Mustard",
      "type" : "SAUCE",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/data-api/ingredients/MTRD"
        },
        "ingredient" : {
          "href" : "http://localhost:8080/data-api/ingredients/MTRD"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/data-api/ingredients"
    },
    "profile" : {
      "href" : "http://localhost:8080/data-api/profile/ingredients"
    }
  }
}

How I try to get an object:

public List<Ingredient> getAllIngredients() {
    return rest.exchange("http://localhost:8080/data-api/ingredients",
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<List<Ingredient>>() {}
            ).getBody();
}

Ingredient:

@Data
@RequiredArgsConstructor
@NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
@Entity
public class Ingredient {

  @Id
  private final String id;
  private final String name;
  private final Type type;

  public enum Type {
    BREAD, PROTEIN, VEGGIES, CHEESE, SAUCE
  }

}

When I get only one object by Id getIngredientById("BEEF"), it works fine. What am I doing wrong?

public Ingredient getIngredientById(String ingredientId) {

    return rest.getForObject(
            "http://localhost:8080/data-api/ingredients/{id}",
            Ingredient.class,
            ingredientId);
}

JSON from "http://localhost:8080/data-api/ingredients/BEEF"

{
  "name" : "Beef",
  "type" : "PROTEIN",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/data-api/ingredients/BEEF"
    },
    "ingredient" : {
      "href" : "http://localhost:8080/data-api/ingredients/BEEF"
    }
  }
}

Solution

  • The one problem I can see when the list is being returned it has "_embedded" : { } element as root, so when you deserialize the response to List<Ingradient>it will not work. For single object it is working because you have response with Ingredient {} element is at the root. Either you need to get rid of _embadded from the API response or you have your object modified to incorporate exact structure you received from "http://localhost:8080/data-api/ingredients" API