Jackson 将字符串字段解析为 JSON

问题描述 投票:0回答:4

我有以下 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 对象。

java jackson
4个回答
6
投票

@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]

4
投票
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;
}

0
投票

有几个问题:

    示例中提供的
  • 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);

0
投票

同样的问题。将 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;
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.