diff --git a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonSubtypesSpec.groovy b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonSubtypesSpec.groovy index 781635a6a..925f89b65 100644 --- a/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonSubtypesSpec.groovy +++ b/serde-jackson-tck/src/main/groovy/io/micronaut/serde/jackson/JsonSubtypesSpec.groovy @@ -3,7 +3,6 @@ package io.micronaut.serde.jackson import spock.lang.Issue -// NOTE: Jackson will fail on unknown subtype class JsonSubtypesSpec extends JsonCompileSpec { void 'test json sub types using name deserialization'() { diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/JsonSubtypesSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/JsonSubtypesSpec.groovy deleted file mode 100644 index b4ea752bb..000000000 --- a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/JsonSubtypesSpec.groovy +++ /dev/null @@ -1,417 +0,0 @@ -package io.micronaut.serde.jackson.annotation - -import io.micronaut.serde.jackson.JsonCompileSpec -import spock.lang.Issue - -class JsonSubtypesSpec extends JsonCompileSpec { - - void 'test json sub types using name deserialization'() { - given: - def context = buildContext('test.Base', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes( - @JsonSubTypes.Type(value = Sub.class, name = "sub-class") -) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -@Serdeable -class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} -""") - when: - def baseArg = argumentOf(context, "test.Base") - def result = jsonMapper.readValue('{"type":"sub-class","string":"a","integer":1}', baseArg) - - then: - result.getClass().name == 'test.Sub' - result.string == 'a' - result.integer == 1 - - when: - result = jsonMapper.readValue('{"type":"some-other-type","string":"a","integer":1}', baseArg) - - then: - result.getClass().name != 'test.Sub' - - when: - result = jsonMapper.readValue('{"type":"Sub","string":"a","integer":1}', baseArg) - - then: - result.getClass().name != 'test.Sub' - } - - void 'test json sub types using name deserialization with type name defined'() { - given: - def context = buildContext('test.Base', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes( - @JsonSubTypes.Type(value = Sub.class) -) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -@Serdeable -@JsonTypeName("sub-class") -class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} -""") - when: - def baseArg = argumentOf(context, "test.Base") - def result = jsonMapper.readValue('{"type":"sub-class","string":"a","integer":1}', baseArg) - - then: - result.getClass().name == 'test.Sub' - result.string == 'a' - result.integer == 1 - - when: - result = jsonMapper.readValue('{"type":"some-other-type","string":"a","integer":1}', baseArg) - - then: - result.getClass().name != 'test.Sub' - - when: - result = jsonMapper.readValue('{"type":"Sub","string":"a","integer":1}', baseArg) - - then: - result.getClass().name != 'test.Sub' - } - - void 'test json sub types using name deserialization with names defined'() { - given: - def context = buildContext('test.Base', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes( - @JsonSubTypes.Type(value = Sub.class, names = {"subClass", "sub-class"}) -) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -@Serdeable -@JsonTypeName("SubClass") -class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} -""") - when: - def baseArg = argumentOf(context, "test.Base") - def result = jsonMapper.readValue('{"type":"sub-class","string":"a","integer":1}', baseArg) - - then: - result.getClass().name == 'test.Sub' - - when: - result = jsonMapper.readValue('{"type":"subClass","string":"a","integer":1}', baseArg) - - then: - result.getClass().name == 'test.Sub' - - when: - result = jsonMapper.readValue('{"type":"SubClass","string":"a","integer":1}', baseArg) - - then: - result.getClass().name == 'test.Sub' - } - - void 'test json sub types using name deserialization with wrapper'() { - given: - def context = buildContext('test.Base', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) -@JsonSubTypes( - @JsonSubTypes.Type(value = Sub.class, name = "subClass") -) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -@Serdeable -class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} -""") - when: - def baseArg = argumentOf(context, "test.Base") - def result = jsonMapper.readValue('{"subClass":{"string":"a","integer":1}}', baseArg) - - then: - result.getClass().name == 'test.Sub' - } - - @Issue("https://github.com/micronaut-projects/micronaut-serialization/issues/575") - void 'test wrapper unnesting'() { - given: - def context = buildContext('test.Base', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -record Wrapper(Base base, String other) { -} - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) -@JsonSubTypes( - @JsonSubTypes.Type(value = Sub.class, name = "subClass") -) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -@Serdeable -class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} -""") - when: - def wrapperArg = argumentOf(context, "test.Wrapper") - def result = jsonMapper.readValue('{\"base\":{"subClass":{"string":"a","integer":1}},\"other\":\"foo\"}', wrapperArg) - - then: - result.base.getClass().name == 'test.Sub' - result.other == 'foo' - } - - void 'test json sub types using name serialization'() { - given: - def context = buildContext('test.Base', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = Sub.class, name = "sub-class"), - @JsonSubTypes.Type(value = A.class, names = {"sub-class-a", "subClassA"}), - @JsonSubTypes.Type(value = B.class, name = "ignore", names = {"Ignore"}) -}) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} - -@Serdeable -class A extends Sub { - public A(String string, Integer integer) { - super(string, integer); - } -} - -@JsonTypeName("sub-class-b") -class B extends Sub { - public B(String string, Integer integer) { - super(string, integer); - } -} -""") - when: - def sub = newInstance(context, "test.Sub", "a", 1) - def a = newInstance(context, "test.A", "hello", 2) - def b = newInstance(context, "test.B", "b", 3) - - then: - jsonMapper.writeValueAsString(sub) == '{"type":"sub-class","string":"a","integer":1}' - jsonMapper.writeValueAsString(a) == '{"type":"sub-class-a","string":"hello","integer":2}' - jsonMapper.writeValueAsString(b) == '{"type":"sub-class-b","string":"b","integer":3}' - } - - void 'subtype SerdeImport'() { - given: - def context = buildContext('test.Sub', """ -package test; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.micronaut.serde.annotation.SerdeImport; -import io.micronaut.serde.annotation.Serdeable; - -@Serdeable -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes( - @JsonSubTypes.Type(value = Sub.class, name = "sub-class") -) -class Base { - private String string; - - public Base(String string) { - this.string = string; - } - - public String getString() { - return string; - } -} - -@SerdeImport(Sub.class) -public class Sub extends Base { - private Integer integer; - - public Sub(String string, Integer integer) { - super(string); - this.integer = integer; - } - - public Integer getInteger() { - return integer; - } -} -""") - when: - def baseArg = argumentOf(context, "test.Base") - def result = jsonMapper.readValue('{"type":"sub-class","string":"a","integer":1}', baseArg) - - then: - result.getClass().name == 'test.Sub' - result.string == 'a' - result.integer == 1 - } -} diff --git a/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesSpec.groovy b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesSpec.groovy new file mode 100644 index 000000000..603f00d03 --- /dev/null +++ b/serde-jackson/src/test/groovy/io/micronaut/serde/jackson/annotation/SerdeJsonSubtypesSpec.groovy @@ -0,0 +1,134 @@ +package io.micronaut.serde.jackson.annotation + +import io.micronaut.serde.jackson.JsonSubtypesSpec + +class SerdeJsonSubtypesSpec extends JsonSubtypesSpec { + + // Jackson will fail on unknown type - Serde will deserialize the base type + + void 'test json sub types using name deserialization with unknown type'() { + given: + def context = buildContext('test.Base', """ +package test; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes( + @JsonSubTypes.Type(value = Sub.class, name = "sub-class") +) +class Base { + private String string; + + public Base(String string) { + this.string = string; + } + + public String getString() { + return string; + } +} + +@Serdeable +class Sub extends Base { + private Integer integer; + + public Sub(String string, Integer integer) { + super(string); + this.integer = integer; + } + + public Integer getInteger() { + return integer; + } +} +""") + when: + def baseArg = argumentOf(context, "test.Base") + def result = jsonMapper.readValue('{"type":"sub-class","string":"a","integer":1}', baseArg) + + then: + result.getClass().name == 'test.Sub' + result.string == 'a' + result.integer == 1 + + when: + result = jsonMapper.readValue('{"type":"some-other-type","string":"a","integer":1}', baseArg) + + then: + result.getClass().name != 'test.Sub' + + when: + result = jsonMapper.readValue('{"type":"Sub","string":"a","integer":1}', baseArg) + + then: + result.getClass().name != 'test.Sub' + } + + void 'test json sub types using name deserialization with type name defined with unknown type'() { + given: + def context = buildContext('test.Base', """ +package test; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes( + @JsonSubTypes.Type(value = Sub.class) +) +class Base { + private String string; + + public Base(String string) { + this.string = string; + } + + public String getString() { + return string; + } +} + +@Serdeable +@JsonTypeName("sub-class") +class Sub extends Base { + private Integer integer; + + public Sub(String string, Integer integer) { + super(string); + this.integer = integer; + } + + public Integer getInteger() { + return integer; + } +} +""") + when: + def baseArg = argumentOf(context, "test.Base") + def result = jsonMapper.readValue('{"type":"sub-class","string":"a","integer":1}', baseArg) + + then: + result.getClass().name == 'test.Sub' + result.string == 'a' + result.integer == 1 + + when: + result = jsonMapper.readValue('{"type":"some-other-type","string":"a","integer":1}', baseArg) + + then: + result.getClass().name != 'test.Sub' + + when: + result = jsonMapper.readValue('{"type":"Sub","string":"a","integer":1}', baseArg) + + then: + result.getClass().name != 'test.Sub' + } +}