对 Spring 的 JSON 序列化感到困惑

Confused by Spring's JSON serialization

我有一个用 Java 编写的应用程序,带有 Spring 引导和 Spring MVC,它使用包含字节数组的对象响应获取请求:

public HashedPasswordSpec get() {
    User user = userRepository.findByEmail("example@example.com");
    return user.getHashedPasswordSpec();
}

在客户端,我尝试使用相同的 class:

自动反序列化它
RestTemplate restTemplate = new RestTemplate();
HashedPasswordSpec spec = restTemplate.getForObject("http://localhost:8080/v1/hashed-password-spec", HashedPasswordSpec.class);

失败并出现此错误:

Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 10 path $.salt
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:224)
    at com.google.gson.Gson.fromJson(Gson.java:887)
    at com.google.gson.Gson.fromJson(Gson.java:852)
    at org.springframework.http.converter.json.GsonHttpMessageConverter.readTypeToken(GsonHttpMessageConverter.java:161)
    ... 39 more
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 10 path $.salt
    at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
    at com.google.gson.internal.bind.ArrayTypeAdapter.read(ArrayTypeAdapter.java:70)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:129)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
    ... 42 more

salt就是byte[]。当我得到一个字符串并将其打印出来时:

String spec = restTemplate.getForObject("http://localhost:8080/v1/hashed-password-spec", String.class);
System.out.println(spec);

我看到了这个:

{"salt":"2GKm9SVxsvLmwaydk8heK/eB94HoPR21+2rTmKMjWo0=","algorithm":"SCrypt","cost":12,"blockSize":8,"parallelization":1,"keyLength":256}

如您所见,salt 被序列化为 Base64,但这不是 Gson 开箱即用的字节数组。 Spring Boot 是否扩展 Gson 以将字节数组序列化为 Base64 字符串?如果是这样,RestTemplate 是否省略了这个扩展?它们彼此不相容吗?我在这里错过了什么?

默认情况下,RestTemplate and Spring MVC's infrastructure 都会注册各种 serializers/deserializers,具体取决于它们在类路径中找到的内容。

具体来说,他们正在寻找 Jackson 和 Gson 的 JSON 序列化,如果找到的话,他们都更喜欢 Jackson。

在您的情况下,您的服务器将 byte[] 序列化为 Base64 字符串这一事实表明它正在使用 Jackson,因为这是它的默认策略(参见 ByteArraySerializer)。

而且我们可以从您的错误日志中看到 RestTemplate 无法用 Gson 解析 JSON。 Gson 不使用,也不期望 byte[] 类型的字段使用 Base64 字符串,它只期望包含数字的 JSON 数组。

因此,您的客户端没有将 Jackson 注册为序列化程序,因为它不在类路径中,或者因为您已经注册了 HttpMessageConverter 个不包含 Jackson 变体的实例的自定义列表。