java - Spring Boot 使用带有通用字段的类来使用 REST API,该字段未转换为正确的类型。我究竟做错了什么?

标签 java json spring-boot jackson

示例代码如下。

我想使用 Spring Boot 2.2 与 REST API 进行通信。我尝试使用的 API 将对象包装在父模型中以进行分页和排序,并将实际对象的 json 数组放入 results field 。我如何为我的 Java 代码建模,以便 jackson “知道”如何将 API 响应反序列化为我的 Java 对象?

我尝试使用 ApiResponse 中的泛型解决此问题,并在执行获取请求时传递预期的字段类型:

String URL_GET_DOGS = "https://localhost/api/v1/dogs/"
ApiResponse<Dog> response = this.restTemplate.getForObject(URL_GET_DOGS, response.getClass());

这会编译并运行...

预期结果:成功创建一个 ApiResponse 对象,其结果字段由狗列表组成。

实际结果:成功创建了一个 ApiResponse 对象,但结果字段是一个对象列表。

所以 jackson 不会正确地转换结果列表,而我似乎得到了一个 List<Object>而不是 List<Dog>对于我的 results我的 ApiResponse 对象中的字段。这样我最终得到错误类型的属性或我根本不想反序列化的属性!请参阅汽车示例。

现在我回到了基于接口(interface)的解决方案,但我被卡住了。 Jackson(理所当然,因为没有办法推断出正确的类......)提示说它没有知道如何反序列化抽象类型,我需要提供一个具体的实现,我不能使用类类型 Jackson 注释,如 here 所述因为我不控制生成响应的 API。

我现在看到的唯一解决方法是为每种类型的响应使用类,但这意味着有很多用于分页和排序字段的重复代码。我做错了什么?

示例 JSON:

{
    "count": 84,
    "next": "http://localhost:80/api/v1/dogs/?limit=2&offset=2",
    "previous": null,
    "results": [
        {
            "name": "Pebbles"
        },
        {
            "name": "Spot"
        }
    ]
}

和另一个端点:

{
    "count": 22,
    "next": "http://localhost:80/api/v1/cars/?limit=2&offset=2",
    "previous": null,
    "results": [
        {
            "brand": "Mercedes",
            "horse_power": 120,
            "field_i_dont": "want_to_deserialize"
        },
        {
            "brand": "BMW",
            "horse_power": 180,
            "field_i_dont": "want_to_deserialize"
        }
    ]
}

示例代码:

public class ApiResponse<T>{

    // paging and sorting
    private Long count;
    private String next;
    private String previous;
    // the actual objects
    private List<T> results;

    // No-args constructor, getters & setters

}

public class Dog {
     private String name;
    // No-args constructor, getters & setters

}

@JsonIgnoreProperties(ignoreUnknown = true)
public class Car {
     private int horsePower;
     private String brand;
    // No-args constructor, getters & setters

}

最佳答案

原因

经过进一步调查,我发现这是由类型删除引起的,答案 here on stackoverflow提供了使用 Jackson 处理此问题的片段。

根据我现在的理解,调用 response.getClass() 与调用 ApiResponse.class 完全相同。尽管 ApiResponse 被类型化为具有泛型参数的类,但由于名为“Type Erasure”的编译器规则,泛型“丢失”了它们的类型参数。 Internal 泛型转换为 Object 类型,Jackson 将使用 LinkedHashMap 来表示 Object 字段中的任何数据。

Generics are used for tighter type checks at compile time and to provide a generic programming. To implement generic behaviour, java compiler apply type erasure. Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.

Type Erasure rules

  • Replace type parameters in generic type with their bound if bounded type parameters are used.
  • Replace type parameters in generic type with Object if unbounded type parameters are used.
  • Insert type casts to preserve type safety.
  • Generate bridge methods to keep polymorphism in extended generic types.

解决方案

为了“修复”我原来问题中的例子

ApiResponse<Dog> response = this.restTemplate.getForObject(URL_GET_DOGS, response.getClass())

必须进行更改,以便可以使用 JavaType 类映射原始 JSON:

ObjectMapper mapper = new ObjectMapper(); // Defined as final in rest-client class.
String rawJsonResponse = this.restTemplate.getForObject(URL_GET_DOGS, String.class)
ApiResponse<Dog> response = mapper.readValue(
    rawJsonResponse,
    mapper.getTypeFactory().constructParametricType(
        ApiResponse.class,
        Dog.class)
    );

关于java - Spring Boot 使用带有通用字段的类来使用 REST API,该字段未转换为正确的类型。我究竟做错了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58522100/

相关文章:

python - 如何从已解析的 json 中提取具有特定键的数据?

java - 实体映射中序列的增量大小设置为[50],而关联的数据库序列增量大小为1

spring-boot - Spring Boot 中带有 Gradle 的 Mapstruct + Lombok(未找到 Bean)

java - Spring Boot 2.0 中的 EmbeddedServletContainerAutoConfiguration 和 ServerPropertiesAutoConfiguration

Java并发 - 虚假唤醒和等待(超时)

iphone - 在 JSON 中是什么意思

java - 无法在 Netbeans 之外运行 javafx JAR

javascript - 将 angular $http 与 django urlpatterns 一起使用以获取 json 文件

java - 共享一个java项目(不同的IDE)

java - C++ 到 Java 的翻译