import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import tools.jackson.databind.json.JsonMapper; public class JsonPayment { // Two annotations tell Jackson which JSON field names the type, and which class each name means. @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = Card.class, name = "card"), @JsonSubTypes.Type(value = Upi.class, name = "upi"), @JsonSubTypes.Type(value = NetBanking.class, name = "netbanking")}) sealed interface Payment permits Card, Upi, NetBanking {} record Card(String last4, long amount) implements Payment {} record Upi(String vpa, long amount) implements Payment {} record NetBanking(String bank, long amount) implements Payment {} static long fee(Payment p) { return switch (p) { case Card(String last4, long amount) -> amount * 2 / 100; case Upi(String vpa, long amount) -> 0; case NetBanking(String bank, long amount) -> 15; }; } public static void main(String[] args) { JsonMapper mapper = JsonMapper.builder().build(); Payment[] all = { new Card("4242", 500), new Upi("ankur@bank", 500), new NetBanking("HDFC", 500) }; for (Payment p : all) { String json = mapper.writeValueAsString(p); Payment back = mapper.readValue(json, Payment.class); System.out.println(json + " -> " + back + " equal=" + back.equals(p) + " fee=" + fee(back)); } try { mapper.readValue("{\"type\":\"cheque\",\"amount\":500}", Payment.class); } catch (Exception e) { System.out.println("unknown type: " + e.getClass().getSimpleName() + ": " + e.getMessage().lines().findFirst().orElse("")); } } }