From de77319b355da002c6cb5cc251f5481c3c29e90c Mon Sep 17 00:00:00 2001 From: Iulian Manda Date: Sun, 17 Apr 2022 14:16:25 +0300 Subject: [PATCH] BAEL-5115 Deduction-Based Polymorphism in Jackson 2.12 (#11732) * BAEL-5115 Deduction-Based Polymorphism in Jackson 2.12 * Fix pmd * Code review changes * Improvements * Code review * Code review * fix typo * Rename package * Apply formatter * Add old deduction * revert --- .../deductionbasedpolymorphism/Character.java | 12 +++ .../ControlledCharacter.java | 14 ++++ .../ImperialSpy.java | 5 ++ .../deductionbasedpolymorphism/King.java | 14 ++++ .../deductionbasedpolymorphism/Knight.java | 14 ++++ .../NamedCharacter.java | 14 ++++ .../CaseInsensitiveInferenceUnitTest.java | 33 +++++++++ .../ContainedInferenceUnitTest.java | 73 +++++++++++++++++++ .../JsonStringFormatterUtil.java | 9 +++ .../SimpleInferenceUnitTest.java | 61 ++++++++++++++++ 10 files changed, 249 insertions(+) create mode 100644 jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Character.java create mode 100644 jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ControlledCharacter.java create mode 100644 jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ImperialSpy.java create mode 100644 jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/King.java create mode 100644 jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Knight.java create mode 100644 jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/NamedCharacter.java create mode 100644 jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/CaseInsensitiveInferenceUnitTest.java create mode 100644 jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/ContainedInferenceUnitTest.java create mode 100644 jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/JsonStringFormatterUtil.java create mode 100644 jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/SimpleInferenceUnitTest.java diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Character.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Character.java new file mode 100644 index 0000000000..6be7483cc2 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Character.java @@ -0,0 +1,12 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +@JsonTypeInfo(use = Id.DEDUCTION) +@JsonSubTypes({ @Type(ImperialSpy.class), @Type(King.class), @Type(Knight.class) }) +public interface Character { + +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ControlledCharacter.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ControlledCharacter.java new file mode 100644 index 0000000000..9f6f03954e --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ControlledCharacter.java @@ -0,0 +1,14 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +public class ControlledCharacter { + + private Character character; + + public Character getCharacter() { + return character; + } + + public void setCharacter(Character character) { + this.character = character; + } +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ImperialSpy.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ImperialSpy.java new file mode 100644 index 0000000000..ff86966e38 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/ImperialSpy.java @@ -0,0 +1,5 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +public class ImperialSpy implements Character { + +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/King.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/King.java new file mode 100644 index 0000000000..3270d92b3a --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/King.java @@ -0,0 +1,14 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +public class King extends NamedCharacter { + + private String land; + + public String getLand() { + return land; + } + + public void setLand(String land) { + this.land = land; + } +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Knight.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Knight.java new file mode 100644 index 0000000000..197d3b758b --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/Knight.java @@ -0,0 +1,14 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +public class Knight extends NamedCharacter { + + private String weapon; + + public String getWeapon() { + return weapon; + } + + public void setWeapon(String weapon) { + this.weapon = weapon; + } +} diff --git a/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/NamedCharacter.java b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/NamedCharacter.java new file mode 100644 index 0000000000..15892f8659 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/main/java/com/baeldung/jackson/deductionbasedpolymorphism/NamedCharacter.java @@ -0,0 +1,14 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +public class NamedCharacter implements Character { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/CaseInsensitiveInferenceUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/CaseInsensitiveInferenceUnitTest.java new file mode 100644 index 0000000000..cad9e73091 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/CaseInsensitiveInferenceUnitTest.java @@ -0,0 +1,33 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +import static com.baeldung.jackson.deductionbasedpolymorphism.JsonStringFormatterUtil.formatJson; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + +class CaseInsensitiveInferenceUnitTest { + + private final ObjectMapper objectMapper = JsonMapper.builder() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .build(); + + @Test + void givenACaseInsensitiveKnight_whenMapping_thenExpectKnight() throws Exception { + String knightJson = formatJson("{'NaMe':'Ostrava, of Boletaria', 'WeaPON':'Rune Sword'}"); + + Character character = objectMapper.readValue(knightJson, Character.class); + + assertTrue(character instanceof Knight); + assertSame(character.getClass(), Knight.class); + Knight knight = (Knight) character; + assertEquals("Ostrava, of Boletaria", knight.getName()); + assertEquals("Rune Sword", knight.getWeapon()); + } + +} diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/ContainedInferenceUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/ContainedInferenceUnitTest.java new file mode 100644 index 0000000000..810051f2ab --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/ContainedInferenceUnitTest.java @@ -0,0 +1,73 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +import static com.baeldung.jackson.deductionbasedpolymorphism.JsonStringFormatterUtil.formatJson; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + +class ContainedInferenceUnitTest { + + private final ObjectMapper objectMapper = JsonMapper.builder() + .build(); + + @Test + void givenAKnightControlledCharacter_whenMapping_thenExpectAControlledCharacterWithKnight() throws Exception { + String controlledCharacterJson = formatJson("{'character': {'name': 'Ostrava, of Boletaria', 'weapon': 'Rune Sword'}}"); + + ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class); + Character character = controlledCharacter.getCharacter(); + + assertTrue(character instanceof Knight); + assertSame(character.getClass(), Knight.class); + Knight knight = (Knight) character; + assertEquals("Ostrava, of Boletaria", knight.getName()); + assertEquals("Rune Sword", knight.getWeapon()); + } + + @Test + void givenAKingControlledCharacter_whenMapping_thenExpectAControlledCharacterWithKing() throws Exception { + String controlledCharacterJson = formatJson("{'character': {'name': 'King Allant', 'land': 'Boletaria'}}"); + + ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class); + Character character = controlledCharacter.getCharacter(); + + assertTrue(character instanceof King); + assertSame(character.getClass(), King.class); + King king = (King) character; + assertEquals("King Allant", king.getName()); + assertEquals("Boletaria", king.getLand()); + } + + @Test + void givenAnEmptySubtype_whenMapping_thenExpectImperialSpy() throws Exception { + String controlledCharacterJson = formatJson("{'character': {}}"); + + ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class); + + assertTrue(controlledCharacter.getCharacter() instanceof ImperialSpy); + } + + @Test + void givenANullCharacter_whenMapping_thenExpectNullCharacter() throws Exception { + String controlledCharacterJson = formatJson("{'character': null}"); + + ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class); + + assertNull(controlledCharacter.getCharacter()); + } + + @Test + void givenAAnAbsentCharacter_whenMapping_thenExpectNullCharacter() throws Exception { + String controlledCharacterJson = formatJson("{}"); + + ControlledCharacter controlledCharacter = objectMapper.readValue(controlledCharacterJson, ControlledCharacter.class); + + assertNull(controlledCharacter.getCharacter()); + } +} diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/JsonStringFormatterUtil.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/JsonStringFormatterUtil.java new file mode 100644 index 0000000000..948b264c45 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/JsonStringFormatterUtil.java @@ -0,0 +1,9 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +public class JsonStringFormatterUtil { + + public static String formatJson(String input) { + return input.replaceAll("'", "\""); + } + +} diff --git a/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/SimpleInferenceUnitTest.java b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/SimpleInferenceUnitTest.java new file mode 100644 index 0000000000..5e63e95289 --- /dev/null +++ b/jackson-modules/jackson-annotations/src/test/java/com/baeldung/jackson/deductionbasedpolymorphism/SimpleInferenceUnitTest.java @@ -0,0 +1,61 @@ +package com.baeldung.jackson.deductionbasedpolymorphism; + +import static com.baeldung.jackson.deductionbasedpolymorphism.JsonStringFormatterUtil.formatJson; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + +class SimpleInferenceUnitTest { + + private final ObjectMapper objectMapper = JsonMapper.builder() + .build(); + + @Test + void givenAKnight_whenMapping_thenExpectAKnightType() throws Exception { + String knightJson = formatJson("{'name':'Ostrava, of Boletaria', 'weapon':'Rune Sword'}"); + + Character character = objectMapper.readValue(knightJson, Character.class); + + assertTrue(character instanceof Knight); + assertSame(character.getClass(), Knight.class); + Knight king = (Knight) character; + assertEquals("Ostrava, of Boletaria", king.getName()); + assertEquals("Rune Sword", king.getWeapon()); + } + + @Test + void givenAKing_whenMapping_thenExpectAKingType() throws Exception { + String kingJson = formatJson("{'name':'Old King Allant', 'land':'Boletaria'}"); + + Character character = objectMapper.readValue(kingJson, Character.class); + + assertTrue(character instanceof King); + assertSame(character.getClass(), King.class); + King king = (King) character; + assertEquals("Old King Allant", king.getName()); + assertEquals("Boletaria", king.getLand()); + } + + @Test + void givenAnEmptyObject_whenMapping_thenExpectAnImperialSpy() throws Exception { + String imperialSpyJson = "{}"; + + Character character = objectMapper.readValue(imperialSpyJson, Character.class); + + assertTrue(character instanceof ImperialSpy); + } + + @Test + void givenANullObject_whenMapping_thenExpectANullObject() throws Exception { + Character character = objectMapper.readValue("null", Character.class); + + assertNull(character); + } + +}