使用 Jackson 解码和反序列化其值为 base64 编码、字符串化 JSON blob 的字段的最简单方法

Simplest way to decode and deserialize field whose value is a base64 encoded, stringified JSON blob with Jackson

在我们的 spring 数据项目中,我们使用 "standard" 方法来编写我们的 DTO,其中我们使用 lombok 的 @Value@Builder 来实现不变性,并且 @JsonDeserialize(builder = SomeClass.SomeClassBuilder.class)用于杰克逊反序列化。

这是一个最小的例子:

@RestController
class Controller {
    @PostMapping("/post")
    void post(@RequestBody PostBody body) {
        System.out.println(body);
    }
}

@Value
@Builder
@JsonDeserialize(builder = PostBody.PostBodyBuilder.class)
class PostBody {

    byte[] id;

    ClientData clientData;

    @JsonPOJOBuilder(withPrefix = "")
    public static class PostBodyBuilder {}
}

@Value
@Builder
@JsonDeserialize(builder = ClientData.ClientDataBuilder.class)
class ClientData {
    String something;
    Integer somethingElse;


    @JsonPOJOBuilder(withPrefix = "")
    public static class ClientDataBuilder {}
}

正如您所期望的那样,使用正常的 JSON 有效负载,它工作正常,例如:

{
  "id": "c29tZWlk",
  "clientData": {
    "something": "somethingValue",
    "somethingElse": 1
  }
}

但是,我们有一个用例,其中 clientData 结构是已知的,但是由于 原因 ,它作为 base64 编码、字符串化的 JSON blob 发送,例如:

{
  "id": "c29tZWlk",
  "clientData": "eyJzb21ldGhpbmciOiJzb21ldGhpbmdWYWx1ZSIsInNvbWV0aGluZ0Vsc2UiOjF9"
}

如果我们能够在 PostBody 调用运行 ClientData 的反序列化器之前透明地解码和取消字符串化该字段作为 PostBody 反序列化的一部分,那就太好了。

一个解决方案是为 PostBody 创建一个自定义反序列化器,但在一个真实的例子中,有更多的字段需要手动处理。

我已经尝试创建一个自定义的 ClientData 解串器,但我很难理解可用的无数不同类型的解串器接口。

到目前为止我有这样的东西:

@Value
@Builder
@JsonDeserialize(builder = PostBody.PostBodyBuilder.class)
class PostBody {

    byte[] id;

    @JsonDeserialize(using = ClientDataBase64Deserializer.class)
    ClientData clientData;

    @JsonPOJOBuilder(withPrefix = "")
    public static class PostBodyBuilder {}
}

// SNIP

class ClientDataBase64Deserializer extends StdScalarDeserializer<ClientData> {

    protected ClientDataBase64Deserializer() {
        super(ClientData.class);
    }

    @Override
    public ClientData deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        byte[] value = Base64.getDecoder().decode(jsonParser.getText());
        System.out.println(new String(value)); // prints stringified JSON
        jsonParser.setCurrentValue(/* somehow convert stringified JSON to a Tree Node? */ value);
        return deserializationContext.readValue(jsonParser, ClientData.class);
    }
}

对于如何推进此示例的任何想法,或者我可能缺少的一些其他机制来解决此问题,我将不胜感激?

干杯

以真正的 SO 方式,我在提出问题几分钟后设法解决了我的问题。

ClientDataBase64Deserializer 和 PostBody 的实现按预期工作:

@Value
@Builder
@JsonDeserialize(builder = PostBody.PostBodyBuilder.class)
public class PostBody {

    byte[] id;

    ClientData clientData;

    public interface IPostBodyBuilder {
        @JsonDeserialize(using = ClientDataBase64Deserializer.class)
        PostBody.PostBodyBuilder clientData(ClientData clientData);
    }

    @JsonPOJOBuilder(withPrefix = "")
    public static class PostBodyBuilder implements IPostBodyBuilder {}
}


class ClientDataBase64Deserializer extends StdScalarDeserializer<ClientData> {
    private final ObjectMapper objectMapper;

    protected ClientDataBase64Deserializer(ObjectMapper objectMapper) {
        super(ClientData.class);
        this.objectMapper = objectMapper;
    }

    @Override
    public ClientData deserialize(
            JsonParser jsonParser, DeserializationContext deserializationContext
    ) {
        byte[] value = jsonParser.readValueAs(byte[].class);
        return objectMapper.readValue(value, ClientData.class);
    }
}