From 52b2365f69454348b7354c3ef19803bfcfeaf028 Mon Sep 17 00:00:00 2001 From: asmhatre Date: Tue, 4 Aug 2026 17:30:23 +0000 Subject: [PATCH] Part 2: sealed hierarchy with explicit @JsonSubTypes registry --- .../C03SealedWithSubTypes.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/com/ankurm/jackson3/part2modernjava/C03SealedWithSubTypes.java diff --git a/src/main/java/com/ankurm/jackson3/part2modernjava/C03SealedWithSubTypes.java b/src/main/java/com/ankurm/jackson3/part2modernjava/C03SealedWithSubTypes.java new file mode 100644 index 0000000..a0da051 --- /dev/null +++ b/src/main/java/com/ankurm/jackson3/part2modernjava/C03SealedWithSubTypes.java @@ -0,0 +1,57 @@ +package com.ankurm.jackson3.part2modernjava; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.json.JsonMapper; + +import java.util.List; + +/** + * Post: Jackson with Records, Optionals, Sealed Classes + * https://ankurm.com/jackson-java-records-optionals/ + * Section: "Jackson with Sealed Classes (Java 17+)" + * + * The explicit-registry form: @JsonTypeInfo plus a hand-maintained @JsonSubTypes. + * Compare with C04SealedAutoDiscovery, which drops the registry entirely. + */ +public class C03SealedWithSubTypes { + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "shapeType") + @JsonSubTypes({ + @JsonSubTypes.Type(value = Circle.class, name = "circle"), + @JsonSubTypes.Type(value = Rectangle.class, name = "rectangle") + }) + public sealed interface Shape permits Circle, Rectangle { } + + public record Circle(double radius) implements Shape { } + public record Rectangle(double width, double height) implements Shape { } + + public static void main(String[] args) { + JsonMapper mapper = JsonMapper.builder().build(); + + String json = "[" + + "{\"shapeType\":\"circle\",\"radius\":5.0}," + + "{\"shapeType\":\"rectangle\",\"width\":10.0,\"height\":4.0}" + + "]"; + + List shapes = mapper.readValue(json, new TypeReference>() { }); + + // Java 21 pattern matching for switch — exhaustive because Shape is sealed, + // so no default branch is needed and a new permitted type is a compile error. + for (Shape shape : shapes) { + String description = switch (shape) { + case Circle c -> "Circle with radius: " + c.radius(); + case Rectangle r -> "Rectangle " + r.width() + " x " + r.height(); + }; + System.out.println(description); + } + + // CAREFUL: writeValueAsString(List) loses the discriminator, because + // the runtime type of the list carries no element type for Jackson to read. + // The result does not round-trip. See part5polymorphic/F02 for the full story. + System.out.println("lossy : " + mapper.writeValueAsString(shapes)); + System.out.println("correct : " + mapper.writerFor(new TypeReference>() { }) + .writeValueAsString(shapes)); + } +}