From e4c43225b93bed075d5edca626351b9bc372a9d4 Mon Sep 17 00:00:00 2001 From: Graham Cox Date: Tue, 9 Apr 2024 07:59:09 +0100 Subject: [PATCH] BAEL-7335: Polymorphism with Gson (#16320) --- .../com/baeldung/gson/polymorphic/Shape.java | 5 + .../gson/polymorphic/ShapeTypeAdapter.java | 27 ++++ .../gson/polymorphic/TypeAdapterUnitTest.java | 117 ++++++++++++++ .../gson/polymorphic/TypeFieldUnitTest.java | 67 ++++++++ .../gson/polymorphic/WrapperUnitTest.java | 148 ++++++++++++++++++ 5 files changed, 364 insertions(+) create mode 100644 json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/Shape.java create mode 100644 json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/ShapeTypeAdapter.java create mode 100644 json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeAdapterUnitTest.java create mode 100644 json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeFieldUnitTest.java create mode 100644 json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/WrapperUnitTest.java diff --git a/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/Shape.java b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/Shape.java new file mode 100644 index 0000000000..b5d32a529b --- /dev/null +++ b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/Shape.java @@ -0,0 +1,5 @@ +package com.baeldung.gson.polymorphic; + +public interface Shape { + double getArea(); +} diff --git a/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/ShapeTypeAdapter.java b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/ShapeTypeAdapter.java new file mode 100644 index 0000000000..cbdff9c6be --- /dev/null +++ b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/ShapeTypeAdapter.java @@ -0,0 +1,27 @@ +package com.baeldung.gson.polymorphic; + +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class ShapeTypeAdapter implements JsonSerializer, JsonDeserializer { + @Override + public JsonElement serialize(Shape shape, Type type, JsonSerializationContext context) { + JsonElement elem = new Gson().toJsonTree(shape); + elem.getAsJsonObject().addProperty("type", shape.getClass().getName()); + return elem; + } + + @Override + public Shape deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + String typeName = jsonObject.get("type").getAsString(); + + try { + Class cls = (Class) Class.forName(typeName); + return new Gson().fromJson(json, cls); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e); + } + } +} diff --git a/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeAdapterUnitTest.java b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeAdapterUnitTest.java new file mode 100644 index 0000000000..81d5f5a9f2 --- /dev/null +++ b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeAdapterUnitTest.java @@ -0,0 +1,117 @@ +package com.baeldung.gson.polymorphic; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TypeAdapterUnitTest { + @Test + void testSerialize() { + List shapes = Arrays.asList( + new Circle(4d), + new Square(5d) + ); + + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeHierarchyAdapter(Shape.class, new ShapeTypeAdapter()); + Gson gson = builder.create(); + + String json = gson.toJson(shapes); + + assertEquals("[" + + "{" + + "\"radius\":4.0," + + "\"area\":50.26548245743669," + + "\"type\":\"com.baeldung.gson.polymorphic.TypeAdapterUnitTest$Circle\"" + + "},{" + + "\"side\":5.0," + + "\"area\":25.0," + + "\"type\":\"com.baeldung.gson.polymorphic.TypeAdapterUnitTest$Square\"" + + "}]", json); + } + + + @Test + void testDeserializeWrapper() { + List shapes = Arrays.asList( + new Circle(4d), + new Square(5d) + ); + + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeHierarchyAdapter(Shape.class, new ShapeTypeAdapter()); + Gson gson = builder.create(); + + String json = gson.toJson(shapes); + + Type collectionType = new TypeToken>(){}.getType(); + List result = gson.fromJson(json, collectionType); + + assertEquals(shapes, result); + } + + private static class Square implements Shape { + private final double side; + private final double area; + + public Square(double side) { + this.side = side; + this.area = side * side; + } + + @Override + public double getArea() { + return area; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Square square = (Square) o; + return Double.compare(square.side, side) == 0 && Double.compare(square.area, area) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(side, area); + } + } + + private static class Circle implements Shape { + private final double radius; + + private final double area; + + public Circle(double radius) { + this.radius = radius; + this.area = Math.PI * radius * radius; + } + + @Override + public double getArea() { + return area; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Circle circle = (Circle) o; + return Double.compare(circle.radius, radius) == 0 && Double.compare(circle.area, area) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(radius, area); + } + } +} diff --git a/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeFieldUnitTest.java b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeFieldUnitTest.java new file mode 100644 index 0000000000..e6917b2065 --- /dev/null +++ b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/TypeFieldUnitTest.java @@ -0,0 +1,67 @@ +package com.baeldung.gson.polymorphic; + +import com.google.gson.Gson; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TypeFieldUnitTest { + @Test + void testSerialize() { + List shapes = Arrays.asList( + new Circle(4d), + new Square(5d) + ); + + Gson gson = new Gson(); + String json = gson.toJson(shapes); + + assertEquals("[" + + "{" + + "\"type\":\"circle\"," + + "\"radius\":4.0," + + "\"area\":50.26548245743669" + + "},{" + + "\"type\":\"square\"," + + "\"side\":5.0," + + "\"area\":25.0" + + "}]", json); + } + + private static class Square implements Shape { + private final String type = "square"; + private final double side; + private final double area; + + public Square(double side) { + this.side = side; + this.area = side * side; + } + + @Override + public double getArea() { + return area; + } + } + + private static class Circle implements Shape { + private final String type = "circle"; + private final double radius; + + private final double area; + + public Circle(double radius) { + this.radius = radius; + this.area = Math.PI * radius * radius; + } + + @Override + public double getArea() { + return area; + } + } + +} diff --git a/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/WrapperUnitTest.java b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/WrapperUnitTest.java new file mode 100644 index 0000000000..841f9c64d5 --- /dev/null +++ b/json-modules/gson-2/src/test/java/com/baeldung/gson/polymorphic/WrapperUnitTest.java @@ -0,0 +1,148 @@ +package com.baeldung.gson.polymorphic; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class WrapperUnitTest { + @Test + void testSerializeWrapper() { + List shapes = Arrays.asList( + new Wrapper(new Circle(4d)), + new Wrapper(new Square(5d)) + ); + + Gson gson = new Gson(); + String json = gson.toJson(shapes); + + assertEquals("[" + + "{" + + "\"circle\":{" + + "\"radius\":4.0," + + "\"area\":50.26548245743669" + + "}" + + "},{" + + "\"square\":{" + + "\"side\":5.0," + + "\"area\":25.0" + + "}" + + "}]", json); + } + + @Test + void testDeserializeWrapper() { + List shapes = Arrays.asList( + new Wrapper(new Circle(4d)), + new Wrapper(new Square(5d)) + ); + + Gson gson = new Gson(); + String json = gson.toJson(shapes); + + Type collectionType = new TypeToken>(){}.getType(); + List result = gson.fromJson(json, collectionType); + + assertEquals(shapes, result); + } + + private static class Square implements Shape { + private final double side; + private final double area; + + public Square(double side) { + this.side = side; + this.area = side * side; + } + + @Override + public double getArea() { + return area; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Square square = (Square) o; + return Double.compare(square.side, side) == 0 && Double.compare(square.area, area) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(side, area); + } + } + + private static class Circle implements Shape { + private final double radius; + + private final double area; + + public Circle(double radius) { + this.radius = radius; + this.area = Math.PI * radius * radius; + } + + @Override + public double getArea() { + return area; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Circle circle = (Circle) o; + return Double.compare(circle.radius, radius) == 0 && Double.compare(circle.area, area) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(radius, area); + } + } + + private static class Wrapper { + private final Circle circle; + private final Square square; + + public Wrapper(Circle circle) { + this.circle = circle; + this.square = null; + } + + public Wrapper(Square square) { + this.square = square; + this.circle = null; + } + + public Circle getCircle() { + return circle; + } + + public Square getSquare() { + return square; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Wrapper wrapper = (Wrapper) o; + return Objects.equals(circle, wrapper.circle) && Objects.equals(square, wrapper.square); + } + + @Override + public int hashCode() { + return Objects.hash(circle, square); + } + } + +}