使用 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);
}
}
在我们的 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);
}
}