我有以下 JSON:
{
"some_key": "{\"a\": 1, \"b\": \"text\"}"
}
如您所见,
some_key
字段不是 JSON 对象,它是一个字符串,包含有效的 JSON。
我想将其解析为以下结构:
class Foo {
Bar some_key;
}
class Bar {
int a;
String b;
}
更新:
类 A 和 B,有 getter 和 setter、构造函数。我还没显示 它们使示例保持简短。
我无法编辑 JSON 的奇怪结构。
问题是如何让 Jackson 将内部字符串字段解析为 JSON 对象。
@t_liang 非常接近 - 只是需要比注释多一点的空间来显示工作示例......
与:
class Foo {
@JsonDeserialize(using = BarDeserializer.class)
private Bar some_key;
}
那么当前的
ObjectMapper
其实就是JsonParser.getCodec()
:
public class BarDeserializer extends JsonDeserializer<Bar> {
@Override
public Bar deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = p.getText();
ObjectMapper mapper = (ObjectMapper) p.getCodec();
return mapper.readValue(text, Bar.class);
}
}
然后这个...
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(
"{ \"some_key\": \"{\\\"a\\\": 1, \\\"b\\\": \\\"text\\\"}\" }",
Foo.class);
System.out.println(foo.getSome_key());
...显示预期值:
Bar [a=1, b=text]
step 1:
public class BarDeserializer extends JsonDeserializer<Bar> {
@Override
public Bar deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = p.getText();
return <your ObjectMapper>.readValue(text, Bar.class);
}
}
step 2:
class Foo {
@JsonDeserialize(using=BarDeserializer.class)
Bar some_key;
}
or
@JsonDeserialize(using=BarDeserializer.class)
class Bar {
int a;
String b;
}
有几个问题:
json
与模式不对应。例如。根据 json
,some_key
是一个字符串字段(用双引号括起来),而根据架构,它是一个 Boo
字段。您需要将 json 更改为如下所示(或将 some_value
更改为 String 类型):
{
"some_key": {\"a\": 1, \"b\": \"text\"}
}
没有 getter 或 setter。
Jackson
无法反序列化,无法通过反射访问 getter/setter。这是工作示例:
class Foo {
Bar some_key;
public Bar getSome_key() {
return some_key;
}
public void setSome_key(Bar some_key) {
this.some_key = some_key;
}
}
class Bar {
int a;
String b;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
}
反序列化:
ObjectMapper mapper = new ObjectMapper();
Foo response = mapper.readValue("{\"some_key\": {\"a\": 1, \"b\": \"text\"}}", Foo.class);
同样的问题。将 Vladimir Petrakovich 的评论和一些研究放在一起,我得出了一个似乎适用于任何类的解决方案,包括你的类(我使用了 Java 17,但它也应该适用于早期版本):
public class JSONStringDeserializer<T> extends JsonDeserializer<T> implements ContextualDeserializer {
private JavaType contextualType;
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
try (JsonParser parser = p.getCodec().getFactory().createParser(p.getText())) {
return p.getCodec().readValue(parser, this.contextualType);
}
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException {
this.contextualType = beanProperty.getType();
return this;
}
}
但是,此解决方案对于 n 级别的嵌套无法正常工作,即,如果 JSON 格式的字符串本身包含 JSON 格式的字符串,则反序列化会失败。换句话说,在下面的 JUnit 5 测试类中(使用 Lombok 注解来缩短代码),
test_json_string_deserializer()
成功,而 test_json_string_deserializer_nested()
失败。当然欢迎任何贡献。
class JSONStringDeserializerTest {
@Test
void test_json_string_deserializer() {
try {
/* Arrange */
ObjectMapper objectMapper = new ObjectMapper();
String jsonDto = """
{
"some_key": "{\\"a\\": 1, \\"b\\": \\"text\\"}"
}""";
/* Act */
Foo parsedDto = objectMapper.readValue(jsonDto, Foo.class);
/* Assert */
Foo expectedDto = Foo.builder()
.some_key(Bar.builder()
.a(1)
.b("text")
.build())
.build();
assertThat(parsedDto).isEqualTo(expectedDto);
} catch (JsonProcessingException e) {
fail("Failed to deserialize Foo", e);
}
}
@Test
void test_json_string_deserializer_nested() {
try {
/* Arrange */
ObjectMapper objectMapper = new ObjectMapper();
String jsonDto = """
{
"description":"Example Description",
"nested":"{\\"details\\": \\"{\\"id\\": 12345}\\"}",
"alertState":"Active"
}""";
/* Act */
BeanDTO parsedDto = objectMapper.readValue(jsonDto, BeanDTO.class);
/* Assert */
BeanDTO expectedDto = BeanDTO.builder()
.description("Example Description")
.nested(NestedBeanDTO.builder()
.details(NestedNestedBeanDTO.builder()
.id(12345L)
.build())
.build())
.alertState("Active")
.build();
assertThat(parsedDto).isEqualTo(expectedDto);
} catch (JsonProcessingException e) {
fail("Failed to deserialize BeanDTO", e);
}
}
@Data
@Builder
@Jacksonized
static class Foo {
@JsonDeserialize(using = JSONStringDeserializer.class)
Bar some_key;
}
@Data
@Builder
@Jacksonized
static class Bar {
int a;
String b;
}
@Data
@Builder
@Jacksonized
static class BeanDTO {
private String description;
@JsonDeserialize(using = JSONStringDeserializer.class)
private NestedBeanDTO nested;
private String alertState;
}
@Data
@Builder
@Jacksonized
static class NestedBeanDTO {
@JsonDeserialize(using = JSONStringDeserializer.class)
private NestedNestedBeanDTO details;
}
@Data
@Builder
@Jacksonized
static class NestedNestedBeanDTO {
private Long id;
}
}