Fra*_*ank 2 java json record jackson
根据设计,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"
}
]
}
Run Code Online (Sandbox Code Playgroud)
在“传统”Java 中,你可以这样做
BaseShape with width and height
ShapeA extends BaseShape with label
ShapeB extends BaseShape with length
ShapeC extends BaseShape with URL and color
Run Code Online (Sandbox Code Playgroud)
但我有点固执,真的很想用记录。
我的解决方案现在看起来像这样:
@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
) {
}
Run Code Online (Sandbox Code Playgroud)
我不喜欢重复的代码,这是一个不好的做法......但最终我可以使用这个辅助类来加载它:
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");
}
}
}
Run Code Online (Sandbox Code Playgroud)
事实证明这个测试工作正常:
@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)
);
}
Run Code Online (Sandbox Code Playgroud)
使用 Jackson 库和 Java Records 实现这一目标的最佳方法是什么?
额外的旁注:我还需要能够以与原始格式相同的格式写回 JSON。
记录不能继承,因为它们旨在成为可靠的契约,但它们可以实现接口。JasonSubTypes因此,您可以使用 Jackson 2.12 或更高版本执行类似的操作:
楷模
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record Drawing(
String name,
List<BaseShape> shapes
) { }
// added benefit of interface here is it reminds you to have the default fields
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes({
@JsonSubTypes.Type(ShapeA.class),
@JsonSubTypes.Type(ShapeB.class),
@JsonSubTypes.Type(ShapeC.class)
})
public interface BaseShape {
Integer width();
Integer height();
}
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ShapeA (
Integer width,
Integer height,
String label
) implements BaseShape { }
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ShapeB(
Integer width,
Integer height,
Integer length
) implements BaseShape { }
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ShapeC(
Integer width,
Integer height,
String url,
String color
) implements BaseShape { }
Run Code Online (Sandbox Code Playgroud)
测试班
@Slf4j
class DemoTest {
private ObjectMapper objectMapper = ObjectMapperBuilder.getObjectMapper();
@Test
void test() throws JsonProcessingException {
final String testString = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(
new Drawing(
"happy",
List.of(
new ShapeA(1, 1, "happyShape"),
new ShapeB(2, 2, 3),
new ShapeC(2, 2, "www.shape.com/shape", "blue"
)
)
)
);
log.info("From model to string {}", testString);
Drawing drawing = objectMapper.readValue(testString, Drawing.class);
log.info(
"Captured types {}",
drawing
.shapes()
.stream()
.map(s -> s.getClass().getName())
.collect(Collectors.toSet())
);
log.info(
"From string back to model then again to string {}",
objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(drawing)
);
}
}
Run Code Online (Sandbox Code Playgroud)
这是测试日志输出:
17:06:41.293 [Test worker] INFO com.demo.DemoTest - From model to string {
"name" : "happy",
"shapes" : [ {
"width" : 1,
"height" : 1,
"label" : "happyShape"
}, {
"width" : 2,
"height" : 2,
"length" : 3
}, {
"width" : 2,
"height" : 2,
"url" : "www.shape.com/shape",
"color" : "blue"
} ]
}
17:06:41.353 [Test worker] INFO com.demo.DemoTest - Captured types [com.demo.DemoTest$ShapeB, com.demo.DemoTest$ShapeA, com.demo.DemoTest$ShapeC]
17:06:41.354 [Test worker] INFO com.demo.DemoTest - From string back to model then again to string {
"name" : "happy",
"shapes" : [ {
"width" : 1,
"height" : 1,
"label" : "happyShape"
}, {
"width" : 2,
"height" : 2,
"length" : 3
}, {
"width" : 2,
"height" : 2,
"url" : "www.shape.com/shape",
"color" : "blue"
} ]
}
Run Code Online (Sandbox Code Playgroud)
请注意,您可以将type字段添加为注释name的属性@JsonSubTypes.Type,但是只要记录中的字段不完全相同,无论有或没有鉴别器,都可以使用该字段。
您可以JsonSubtypes 在此处阅读更多相关信息。
| 归档时间: |
|
| 查看次数: |
5865 次 |
| 最近记录: |