Skip to content

Commit

Permalink
Multiple values in @JsonDiscriminatorValue (#180)
Browse files Browse the repository at this point in the history
- if class has field for discriminator value then writer uses it
- if class has now field then writer uses first value from array
- reader just passes value to constructor if field present
  • Loading branch information
Squiry authored Jul 7, 2023
1 parent 9c92438 commit 51ce572
Show file tree
Hide file tree
Showing 17 changed files with 523 additions and 513 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ public class JsonTypes {
public static final ClassName jsonFieldAnnotation = ClassName.get("ru.tinkoff.kora.json.common.annotation", "JsonField");
public static final ClassName jsonSkipAnnotation = ClassName.get("ru.tinkoff.kora.json.common.annotation", "JsonSkip");

public static final ClassName bufferedParserWithDiscriminator = ClassName.get("ru.tinkoff.kora.json.common", "BufferedParserWithDiscriminator");
public static final ClassName bufferingJsonParser = ClassName.get("ru.tinkoff.kora.json.common.util", "BufferingJsonParser");
public static final ClassName discriminatorHelper = ClassName.get("ru.tinkoff.kora.json.common.util", "DiscriminatorHelper");

public static final ClassName jsonParseException = ClassName.get("com.fasterxml.jackson.core", "JsonParseException");
public static final ClassName jsonParser = ClassName.get("com.fasterxml.jackson.core", "JsonParser");
public static final ClassName jsonParserSequence = ClassName.get("com.fasterxml.jackson.core.util", "JsonParserSequence");
public static final ClassName jsonGenerator = ClassName.get("com.fasterxml.jackson.core", "JsonGenerator");
public static final ClassName jsonToken = ClassName.get("com.fasterxml.jackson.core", "JsonToken");
public static final ClassName serializedString = ClassName.get("com.fasterxml.jackson.core.io", "SerializedString");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ru.tinkoff.kora.annotation.processor.common.AnnotationUtils;
import ru.tinkoff.kora.annotation.processor.common.CommonUtils;
import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException;

import javax.annotation.Nullable;
import javax.lang.model.element.Element;
Expand All @@ -10,6 +11,7 @@
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.List;


public class JsonUtils {
Expand Down Expand Up @@ -66,14 +68,17 @@ public static String discriminatorField(Types types, TypeElement element) {
return null;
}

public static String discriminatorValue(TypeElement element) {
public static List<String> discriminatorValue(TypeElement element) {
var annotation = AnnotationUtils.findAnnotation(element, JsonTypes.jsonDiscriminatorValue);
if (annotation != null) {
var value = AnnotationUtils.<String>parseAnnotationValueWithoutDefault(annotation, "value");
var value = AnnotationUtils.<List<String>>parseAnnotationValueWithoutDefault(annotation, "value");
if (value != null) {
if (value.isEmpty()) {
throw new ProcessingErrorException("Discriminator value can't be empty array", element, annotation);
}
return value;
}
}
return element.getSimpleName().toString();
return List.of(element.getSimpleName().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,20 @@ public TypeSpec generateSealedReader(TypeElement jsonElement) {
.returns(ClassName.get(jsonElement))
.addAnnotation(Override.class)
.addAnnotation(Nullable.class);
method.addCode("var bufferedParser = new $T(_parser);\n", JsonTypes.bufferedParserWithDiscriminator);
method.addCode("var discriminator = bufferedParser.getDiscriminator($S);\n", discriminatorField);
method.addCode("var bufferingParser = new $T(_parser);\n", JsonTypes.bufferingJsonParser);
method.addCode("var discriminator = $T.readStringDiscriminator(bufferingParser, $S);\n", JsonTypes.discriminatorHelper, discriminatorField);
method.addCode("if (discriminator == null) throw new $T(_parser, $S);\n", JsonTypes.jsonParseException, "Discriminator required, but not provided");
method.addCode("bufferedParser.resetPosition();\n");
method.addCode("var bufferedParser = $T.createFlattened(false, bufferingParser.reset(), _parser);\n", JsonTypes.jsonParserSequence);
method.addCode("bufferedParser.nextToken();\n");
method.addCode("return switch(discriminator) {$>\n");
for (var elem : permittedSubclasses) {
var readerName = getReaderFieldName(elem);
var discriminatorValue = JsonUtils.discriminatorValue(elem);
method.addCode("case $S -> $L.read(bufferedParser);\n", discriminatorValue, readerName);
var discriminatorValues = JsonUtils.discriminatorValue(elem);
for (var discriminatorValue : discriminatorValues) {
method.addCode("case $S -> $L.read(bufferedParser);\n", discriminatorValue, readerName);
}
}
method.addCode("default -> throw new $T(_parser, $S);$<\n};", JsonTypes.jsonParseException, "Unknown discriminator");
method.addCode("default -> throw new $T(_parser, $S + discriminator + \"'\");$<\n};", JsonTypes.jsonParseException, "Unknown discriminator: '");
typeBuilder.addMethod(method.build());

return typeBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ public TypeSpec generate(JsonClassWriterMeta meta) {

var discriminatorField = JsonUtils.discriminatorField(types, meta.typeElement());
if (discriminatorField != null) {
var discriminatorFieldValue = JsonUtils.discriminatorValue(meta.typeElement());
method.addCode("_gen.writeFieldName($S);\n", discriminatorField);
method.addStatement("_gen.writeString($S);", discriminatorFieldValue);
if (meta.fields().stream().noneMatch(f -> f.jsonName().equals(discriminatorField))) {
var discriminatorFieldValues = JsonUtils.discriminatorValue(meta.typeElement());
method.addCode("_gen.writeFieldName($S);\n", discriminatorField);
method.addStatement("_gen.writeString($S);", discriminatorFieldValues.get(0));
}
}
for (var field : meta.fields()) {
this.addWriteParam(method, field);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,60 @@ record Impl2(int value) implements TestInterface{}
assertThat(m.read(json2.getBytes(StandardCharsets.UTF_8))).isEqualTo(o2);
}

@Test
public void testSealedInterfaceWithField() throws IOException {
compile("""
@Json
@JsonDiscriminatorField("@type")
public sealed interface TestInterface {
@Json
@JsonDiscriminatorValue({"Impl1.1", "Impl1.2"})
record Impl1(@JsonField("@type") String type, String value) implements TestInterface {
public Impl1 {
if (!"Impl1.1".equals(type) && !"Impl1.2".equals(type)) {
throw new IllegalStateException(String.valueOf(type));
}
}
}
@Json
record Impl2(int value) implements TestInterface{}
@Json
@JsonDiscriminatorValue({"Impl3.1", "Impl3.2"})
record Impl3() implements TestInterface {
}
}
""");

var o11 = newObject("TestInterface$Impl1", "Impl1.1", "test");
var json11 = "{\"@type\":\"Impl1.1\",\"value\":\"test\"}";
var o12 = newObject("TestInterface$Impl1", "Impl1.2", "test");
var json12 = "{\"@type\":\"Impl1.2\",\"value\":\"test\"}";

var o2 = newObject("TestInterface$Impl2", 42);
var json2 = "{\"@type\":\"Impl2\",\"value\":42}";

var o3 = newObject("TestInterface$Impl3");
var json31 = "{\"@type\":\"Impl3.1\"}";
var json32 = "{\"@type\":\"Impl3.2\"}";

var m1 = mapper("TestInterface_Impl1");
var m2 = mapper("TestInterface_Impl2");
var m3 = mapper("TestInterface_Impl3");
var m = mapper("TestInterface", List.of(m1, m2, m3), List.of(m1, m2, m3));

assertThat(m.toByteArray(o11)).asString(StandardCharsets.UTF_8).isEqualTo(json11);
assertThat(m.toByteArray(o12)).asString(StandardCharsets.UTF_8).isEqualTo(json12);
assertThat(m.toByteArray(o2)).asString(StandardCharsets.UTF_8).isEqualTo(json2);
assertThat(m.toByteArray(o3)).asString(StandardCharsets.UTF_8).isEqualTo(json31);
assertThat(m.read(json11.getBytes(StandardCharsets.UTF_8))).isEqualTo(o11);
assertThat(m.read(json12.getBytes(StandardCharsets.UTF_8))).isEqualTo(o12);
assertThat(m.read(json2.getBytes(StandardCharsets.UTF_8))).isEqualTo(o2);
assertThat(m.read(json31.getBytes(StandardCharsets.UTF_8))).isEqualTo(o3);
assertThat(m.read(json32.getBytes(StandardCharsets.UTF_8))).isEqualTo(o3);
}

@Test
public void testSealedInterfaceParsingType() throws IOException {
compile("""
Expand Down
Loading

0 comments on commit 51ce572

Please sign in to comment.