skip to content

In REST Assured, how do you deserialize a JSON array response into a List<ScaffoldInspection>?

level: middleimportance: must knowfreq 58%

answer

  1. a Class cannot carry an element type
  2. anonymous subclass, not a bare constructor
  3. io.restassured.common.mapper, not io.restassured.mapper
  4. raw maps become ClassCastException later
  5. no as(TypeRef, ObjectMapperType) overload

basics

~20 s

REST Assured reads a JSON array into a typed list with as(new TypeRef<List<ScaffoldInspection>>() {}); the anonymous subclass carries the element type past erasure. as(List.class) cannot, and hands back raw maps that fail on first use. Import it from io.restassured.common.mapper.

solid answer

~50 s

A JSON array of inspections cannot be read with `extract().as(List.class)`. `List.class` is a plain `Class` with nowhere to record an element type, so the mapper is told only to build a list and fills it with one `Map` per array element; the first typed access then throws `ClassCastException`. REST Assured's answer is `TypeRef`: `extract().as(new TypeRef<List<ScaffoldInspection>>() {})`. The trailing `{}` is load-bearing — `TypeRef` is abstract, and its constructor reads the actual type argument off the anonymous subclass's generic superclass; without one it throws `IllegalArgumentException("No type info in TypeRef")`. Import it from `io.restassured.common.mapper.TypeRef` in the `rest-assured-common` module — the `io.restassured.mapper` spelling that appears in older docs is stale. The same trick covers any generic container, such as `Map<String, List<Defect>>`. Note there is no `as(TypeRef, ObjectMapperType)` overload: to name a mapper as well, pass `typeRef.getType()` into `as(Type, ObjectMapperType)`.

code

java · 18 lines
java
import io.restassured.common.mapper.TypeRef;
import java.util.List;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.greaterThan;

List<ScaffoldInspection> found = given()
        .queryParam("site", "SITE-4417")
        .when()
        .get("/inspections")
        .then()
        .statusCode(200)
        .body("size()", greaterThan(0))
        .extract()
        .as(new TypeRef<List<ScaffoldInspection>>() {});

// element type survived: no raw Map, no ClassCastException
String verdict = found.get(0).getVerdict();

go deeper

for a junior

Learn the shape of the call before the theory: a single object is as(ScaffoldInspection.class) and a JSON array is as(new TypeRef<List<ScaffoldInspection>>() {}). Remember the trailing braces and the io.restassured.common.mapper import.

for a middle

Be ready to explain why a Class literal cannot describe List<Foo>, how the anonymous subclass lets TypeRef recover the argument through getGenericSuperclass, and why the resulting ClassCastException lands far from the as() call.

for a senior

Show judgment about which spelling belongs in a suite: an array Class, a wrapper DTO with an items field, or TypeRef. Point out that a response that will grow a pagination envelope should get the DTO now, not TypeRef.

for a principal

Own the convention. Decide whether the suite maps to hand-written DTOs at all or asserts on paths, and weigh the maintenance cost of DTOs that must track a changing payload against the readability they buy across hundreds of tests.

REST Assured's `as(...)` family hands a response body to an object mapper and gives you a Java object back. For a single resource that is uneventful — `extract().as(ScaffoldInspection.class)`. For a collection endpoint on a scaffolding-inspection API it is not, and the obstacle is the Java type system rather than the library. ## Why `as(List.class)` cannot work `GET /inspections?site=SITE-4417` answers with a top-level JSON array: ```json [ {"ref": "INSP-2024-0917", "verdict": "PASS", "tieCount": 42}, {"ref": "INSP-2024-0918", "verdict": "ADVISORY", "tieCount": 39} ] ``` `extract().as(List.class)` compiles, runs, and then betrays you: - `List.class` is a `Class<List>` — a runtime handle with nowhere to record an element type. - The declaration `List<ScaffoldInspection> found = ... .as(List.class);` raises only an unchecked-conversion warning, never a compile error. - The mapper is told to build "a list" and nothing more, so it fills it with its own generic node type: one `Map` per array element, not one `ScaffoldInspection`. - The failure surfaces at the first typed access as a `ClassCastException`, and the stack trace points at your assertion rather than at the `as(...)` call that caused it. That last point is why this is a mid-level question rather than a trivia question: the symptom and the cause are several lines apart. ## What `TypeRef` does about it `io.restassured.common.mapper.TypeRef<T>` is an abstract class with a `protected` no-argument constructor. That constructor calls `getClass().getGenericSuperclass()` and pulls the first actual type argument out of it, then exposes it through `getType()` as a `java.lang.reflect.Type`. The resolved `Type` — not a bare `Class` — is what REST Assured passes down to the mapper. ```java List<ScaffoldInspection> found = given() .queryParam("site", "SITE-4417") .when().get("/inspections") .then().statusCode(200) .extract().as(new TypeRef<List<ScaffoldInspection>>() {}); ``` Three details are easy to get wrong: 1. The trailing `{}` is not decoration. It creates an anonymous subclass, and that subclass is the only reason `getGenericSuperclass()` is a `ParameterizedType` at runtime. 2. `TypeRef` is abstract, so a bare `new TypeRef<...>()` does not compile at all; a construction that loses the argument throws `IllegalArgumentException("No type info in TypeRef")`. 3. The package is `io.restassured.common.mapper`, in the `rest-assured-common` artifact. Documentation and blog posts that say `io.restassured.mapper.TypeRef` are stale and the import will not resolve. ## The four call forms, side by side | Call | Reader chosen by | Element type | |---|---|---| | `as(ScaffoldInspection.class)` | the response `Content-Type` | not applicable | | `as(ScaffoldInspection.class, ObjectMapperType.GSON)` | the named mapper | not applicable | | `as(new TypeRef<List<ScaffoldInspection>>() {})` | the response `Content-Type` | preserved | | `as(typeRef.getType(), ObjectMapperType.JACKSON_3)` | the named mapper | preserved | Note what the third row does *not* change: `as(TypeRef)` still resolves its reader from the response's content type, exactly like `as(Class)` does. `TypeRef` solves erasure, not reader selection. And there is no `as(TypeRef, ObjectMapperType)` overload — if you need a generic container *and* a named mapper, pass `new TypeRef<List<ScaffoldInspection>>() {}.getType()` into `as(Type, ObjectMapperType)`. ## Failure signatures worth recognising - `ClassCastException` naming a `Map` implementation where you expected your own type — you used `as(List.class)` or an equivalent raw call. - `IllegalArgumentException: No type info in TypeRef` — the anonymous subclass braces went missing, or the reference was built through a helper that erased the argument. - A compile error on `import io.restassured.mapper.TypeRef` — wrong package; use `io.restassured.common.mapper`. - A mapper-side error about an unrecognised field — the element type resolved correctly and the problem has moved on to the DTO, which is progress. ## When not to reach for TypeRef `TypeRef` earns its keep on genuinely generic shapes, but it is not always the shortest correct answer: - `as(ScaffoldInspection[].class)` also works, because an array `Class` carries its component type. It is often the tidier spelling when you only intend to iterate. - `extract().jsonPath().getList("ref", String.class)` is the right tool when you want one field across the array rather than whole objects. - A wrapper DTO — an `InspectionPage` type with a `List<ScaffoldInspection> items` field — removes the problem entirely, and is what you want the moment the endpoint grows a pagination envelope around the array. - Reserve `TypeRef` for shapes an array cannot express: `Map<String, List<Defect>>`, `List<Map<String, Object>>`, and nested containers generally. The judgment an interviewer is listening for is that this is a resolved-`Type` problem, not a REST Assured quirk: the library needs a `java.lang.reflect.Type` to hand the mapper, a `Class` literal can only supply one for non-generic and array shapes, and `TypeRef` is the standard trick for manufacturing one for everything else.

  • Why do the trailing braces in new TypeRef<List<ScaffoldInspection>>() {} matter?
    They create an anonymous subclass. `TypeRef` is abstract and its constructor reads the actual type argument from `getClass().getGenericSuperclass()`, which is a `ParameterizedType` only because of that subclass. Without a type argument the constructor throws `IllegalArgumentException("No type info in TypeRef")`, and without the braces the code does not compile at all.
  • How would you deserialize into a generic container and still name the object mapper explicitly?
    There is no `as(TypeRef, ObjectMapperType)` overload, so you cannot do it in one literal call. Resolve the type yourself and use the `Type` overload: `as(new TypeRef<List<ScaffoldInspection>>() {}.getType(), ObjectMapperType.JACKSON_3)`. `TypeRef.getType()` returns the resolved `java.lang.reflect.Type` that REST Assured would have passed down anyway.
  • Does as(TypeRef) change how REST Assured picks which object mapper reads the body?
    No. `as(TypeRef)` delegates straight to the `as(Type)` path, so the reader is still chosen from the response's content type exactly as it is for `as(Class)`. `TypeRef` solves type erasure only. If the content type is unusable you still need `registerParser`, `defaultParser`, or the two-argument form that names a mapper.

A Class literal is a shipping label that says only "crate". A TypeRef is the packing list taped to the outside: same crate, but now the receiver knows what shape everything inside should be.

saying these in an interview costs you the question

  • Claiming as(List.class) returns a typed list because generics survive at runtime
  • Importing TypeRef from io.restassured.mapper instead of io.restassured.common.mapper
  • Writing new TypeRef<List<Foo>>() without the anonymous-subclass braces
  • Believing TypeRef also changes which object mapper reads the body
  • Passing a Jackson TypeReference straight into as(...) as if it were a TypeRef
  • Assuming a top-level JSON array needs a custom deserializer in REST Assured