当前位置: 首页 > 知识库问答 >
问题:

使用 fasterxml.jackson 将 JSON 解析为 Java 记录

段干弘毅
2023-03-14

Java 记录在设计上不能从另一个对象继承(请参阅为什么 Java 记录不支持继承?)。因此,我想知道实现以下目标的最佳方法是什么。

给定我的 JSON 数据包含具有一些通用数据唯一数据的对象。例如,文字、宽度和高度在所有形状中,但根据类型,它们可以具有其他字段:

{
  "name": "testDrawing",
  "shapes": [
    {
      "type": "shapeA",
      "width": 100,
      "height": 200,
      "label": "test"
    },
    {
      "type": "shapeB",
      "width": 100,
      "height": 200,
      "length": 300
    },
    {
      "type": "shapeC",
      "width": 100,
      "height": 200,
      "url": "www.test.be",
      "color": "#FF2233"
    }
  ]
}

在“传统”Java中,您可以使用

BaseShape with width and height
ShapeA extends BaseShape with label
ShapeB extends BaseShape with length
ShapeC extends BaseShape with URL and color

但我有点固执,真的很想用记录。

我的解决方案现在如下所示:

  • 无基形
  • 公共字段在所有记录中重复
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record Drawing(
        @JsonProperty("name")
        String name,

        @JsonProperty("shapes")
        @JsonDeserialize(using = TestDeserializer.class)
        List<Object> shapes // I don't like the Objects here... 
) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ShapeA (
        @JsonProperty("type") String type,
        @JsonProperty("width") Integer width,
        @JsonProperty("height") Integer height,
        @JsonProperty("label") String label
) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ShapeB(
        @JsonProperty("type") String type,
        @JsonProperty("width") Integer width,
        @JsonProperty("height") Integer height,
        @JsonProperty("length") Integer length
) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ShapeC(
        @JsonProperty("type") String type,
        @JsonProperty("width") Integer width,
        @JsonProperty("height") Integer height,
        @JsonProperty("url") String url,
        @JsonProperty("color") String color
) {
}

我不喜欢重复的代码,这是一种不好的做法......但最后我可以用这个帮助程序类加载它:

public class TestDeserializer extends JsonDeserializer {

    ObjectMapper mapper = new ObjectMapper();

    @Override
    public List<Object> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        List<Object> rt = new ArrayList<>();

        JsonNode node = jsonParser.getCodec().readTree(jsonParser);

        if (node instanceof ArrayNode array) {
            for (Iterator<JsonNode> it = array.elements(); it.hasNext(); ) {
                JsonNode childNode = it.next();
                rt.add(getShape(childNode));
            }
        } else {
            rt.add(getShape(node));
        }

        return rt;
    }

    private Object getShape(JsonNode node) {
        var type = node.get("type").asText();
        switch (type) {
            case "shapeA":
                return mapper.convertValue(node, ShapeA.class);
            case "shapeB":
                return mapper.convertValue(node, ShapeB.class);
            case "shapeC":
                return mapper.convertValue(node, ShapeC.class);
            default:
                throw new IllegalArgumentException("Shape could not be parsed");
        }
    }
}

这个测试被证明工作正常:

@Test
    void fromJsonToJson() throws IOException, JSONException {
        File f = new File(this.getClass().getResource("/test.json").getFile());
        String jsonFromFile = Files.readString(f.toPath());

        ObjectMapper mapper = new ObjectMapper();
        Drawing drawing = mapper.readValue(jsonFromFile, Drawing.class);
        String jsonFromObject = mapper.writeValueAsString(drawing);

        System.out.println("Original:\n" + jsonFromFile.replace("\n", "").replace(" ", ""));
        System.out.println("Generated:\n" + jsonFromObject);

        assertAll(
                //() -> assertEquals(jsonFromFile, jsonFromObject),
                () -> assertEquals("testDrawing", drawing.name()),
                () -> assertTrue(drawing.shapes().get(0) instanceof ShapeA),
                () -> assertTrue(drawing.shapes().get(1) instanceof ShapeB),
                () -> assertTrue(drawing.shapes().get(2) instanceof ShapeC)
        );
    }

使用杰克逊库和Java记录实现此目的的最佳方法是什么?

额外的旁注:我还需要能够以与原始格式相同的格式写回 JSON。

共有1个答案

乌靖
2023-03-14

如果使用常规类,则可以批注父类以包含类型名称作为属性。

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, 
   include = JsonTypeInfo.As.PROPERTY, 
   property = "type")
public class Shape {
}
@JsonTypeName("shapeA")
public class ShapeA extends Shape {
}
 类似资料:
  • 问题内容: 我的样子如下: 现在,每个元素具有相同的结构(假设一个POJO称为MessageDefault.java)。那么我怎么能把所有东西都当作一个??呢? 我正在使用gson进行解析。我也不能更改JSON响应。 编辑:: MessageDefault.java 它只是一切的结构。但是在JSON中,的每个子项都有不同的名称,例如POJO MessageDefault.java中所反映的名称。但

  • 我试图将一个对象转换为json格式,但它不起作用(我得到一个奇怪的堆栈溢出异常)。它从对象到XML都能完美地工作。我有一个简单的实体类User和另一个具有多个关系的类。 相关类 我将@XmlTransient注释放在我想要忽略的getter上。 下面是我的rest服务中的一个方法,它从用户的昵称返回用户 因此,它使用@products({“application/xml”})而不是@product

  • 你好,我有以下任务: 具有JSON对象的URL: *通过注释定义如何将JSON定义到Java列表中,并找到其中有“名称”的对象。 我认为问题是在不使用任何java库的情况下解析JSON。到目前为止,我已经开发了以下代码: 我在这里做的是我有一个JSONObject类,它将JSON属性存储在映射中,然后我想使用反射来填充任何类。 为了解析JSON,我尝试创建一个迷你FSM(:)),它使用For循环解

  • 这里是我被困的地方,我在哪里创建我的新Gson()来在发送数据之前解析它?

  • 问题内容: 我想使用google-gson将json数据解析为java对象。 这是我要解析的数据的示例: IsBean.java User.java RequestObj.java Test.java 错误: 问题答案: 如果没有看到的代码,我不能肯定的说,但是我很有把握地猜测该类有一个类型的字段,您可以使用它来容纳。如果是这样,那么问题在于GSON会扫描该类对象的字段,并且基于字段定义,必须创建

  • 问题内容: 我在MongoDB中的规范化数据模型结构中遇到以下错误: 这是由于以下原因造成的: 具体的部分。我的文档中有一个DBRef对象,因此我可以引用另一个集合中的文档。嵌入式文档结构不是选项。那么我该如何解决呢? 问题答案: 您必须为其导入DBRef编解码器才能进行打印,如果您希望以文档json样式进行打印,则需要编写自己的DBRef编解码器,并将其添加到您给toJson()的编解码器中。

  • 问题内容: 我有一个XML文件,例如 如何将其解析为JSON结构文件? 问题答案: 对于一个简单的解决方案,我建议使用Jackson库,它是一个Java库,用于生成和读取带有XML扩展名的JSON,因为它只需几行简单的代码就可以将任意复杂的XML转换为JSON。 input.xml Java代码: 该演示使用Jackson 1.7.7 (较新的1.7.8也可以使用),Jackson XML Dat

  • 问题内容: 我创建了以下脚本,以便从Oracle SQL Developer从Mobile App DB(基于MongoDB)读取数据: 响应 (l_response_text) 是类似JSON的字符串。例如: 该代码可以正常工作,并将响应插入一个名为 appery的列表中 。但是,我需要解析此响应,以便每个数组进入名为 appery_test 的表中的特定列。表 appery_test 的列数与