ngs-doo/dsl-json

Java annotations over @JsonAttribute cause compile-time error. (Only with Java records)

Closed this issue · 3 comments

unwx commented

I'm trying to set an annotation over a field with @JsonAttribute, but build errors occur.
In my experiments, problems only occur when using java record and annotations with @Target({ElementType.TYPE_USE})

Java: 16+
dsl-json-java8: 1.10.0

Code:

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>unwx</groupId>
    <artifactId>dsl-json-issue</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>dsl-json-issue</name>

    <properties>
        <java.version>16</java.version>
        <maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <dsl-json.version>1.10.0</dsl-json.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.dslplatform</groupId>
            <artifactId>dsl-json-java8</artifactId>
            <version>${dsl-json.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Custom annotation:

@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE_USE})
public @interface CustomAnnotation {
}

Custom enum:

public enum SerializableEnum {
    YES,
    NO
}

Enum converter:

import com.dslplatform.json.JsonReader;
import com.dslplatform.json.JsonWriter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SerializableEnumConverter {
    private static final Map<String, SerializableEnum> stringToEnumMap = new ConcurrentHashMap<>();
    private static final Map<SerializableEnum, String> enumToStringMap = new ConcurrentHashMap<>();

    static {
        final String yes = "yes";
        final String no = "no";

        stringToEnumMap.put(yes, SerializableEnum.YES);
        stringToEnumMap.put(no, SerializableEnum.NO);

        enumToStringMap.put(SerializableEnum.YES, yes);
        enumToStringMap.put(SerializableEnum.NO, no);
    }

    public static final JsonReader.ReadObject<SerializableEnum> JSON_READER = reader -> {
        if (reader.wasNull()) return null;
        final String value = reader.readSimpleString();

        final SerializableEnum v = stringToEnumMap.get(value);
        if (v == null) {
            throw reader.newParseError("error description...");
        }

        return v;
    };

    public static final JsonWriter.WriteObject<SerializableEnum> JSON_WRITER = (writer, value) -> {
        if (value == null) {
            writer.writeNull();
        } else {
            writer.writeString(enumToStringMap.get(value));
        }
    };
}

Class with custom annotations, no errors:

@CompiledJson(onUnknown = CompiledJson.Behavior.FAIL)
public class SerializableClass {
    private static final String SIMPLE_FIELD = "simple_field";
    private static final String ENUM = "enum";

    @CustomAnnotation
    @JsonAttribute(name = SIMPLE_FIELD)
    private final String simpleField;

    @CustomAnnotation
    @JsonAttribute(name = ENUM, converter = SerializableEnumConverter.class)
    private final SerializableEnum serializableEnum;

    public SerializableClass(final String simpleField,
                             final SerializableEnum serializableEnum) {
        this.simpleField = simpleField;
        this.serializableEnum = serializableEnum;
    }

    public String simpleField() {
        return simpleField;
    }

    public SerializableEnum serializableEnum() {
        return serializableEnum;
    }
}

Record, build errors:

@CompiledJson(onUnknown = CompiledJson.Behavior.FAIL)
public record SerializableRecord(
        @CustomAnnotation
        @JsonAttribute(name = SIMPLE_FIELD)
        String simpleField,

        @CustomAnnotation
        @JsonAttribute(name = ENUM, converter = SerializableEnumConverter.class)
        SerializableEnum serializableEnum
) {
    private static final String SIMPLE_FIELD = "simple_field";
    private static final String ENUM = "enum";
}

As said earlier, problems arise when using java record:
Converter:

java: Specified converter: 'unwx.SerializableEnumConverter' has invalid type for JSON_READER field. It must be of type: 'com.dslplatform.json.JsonReader.ReadObject<unwx.@unwx.CustomAnnotation SerializableEnum>'

Without converter, generated sources:

java: <identifier> expected

@javax.annotation.processing.Generated("dsl_json")
public class _SerializableRecord_DslJsonConverter implements com.dslplatform.json.Configuration {
...
private com.dslplatform.json.JsonReader.ReadObject<java.lang.@unwx.CustomAnnotation String> reader_simpleField;
		private com.dslplatform.json.JsonReader.ReadObject<java.lang.@unwx.CustomAnnotation String> reader_simpleField() {
			if (reader_simpleField == null) {
				java.lang.reflect.Type manifest = java.lang.@unwx.CustomAnnotation String.class;
				reader_simpleField = __dsljson.tryFindReader(manifest);
				if (reader_simpleField == null) {
					throw new com.dslplatform.json.ConfigurationException("Unable to find reader for " + manifest + ". Enable runtime conversion by initializing DslJson with new DslJson<>(Settings.basicSetup())");
				}
			}
			return reader_simpleField;
		}
...

Debug:
When using java-record, TypeMirror of annotated field looks very unusual, which ultimately leads to errors:

private TypeMirror unpackType(TypeMirror type) {
}
type.toString() is: 'unwx.@unwx.CustomAnnotation SerializableEnum'
  • Is it possible to use or add something like ((DeclaredType) type).asElement().toString() to get the type without annotations? Tried through the IDEA debugger, it seems to work.
  • Is there any way to bypass this error? Or use only java-class? :(
zapov commented

I just tried creating such test within the DSL-JSON project and it works for me.
Can you create a separate project which shows the problem? It might be some issue with Java version or just the way of how and where classes are set-up.

As for the annotation itself, analysis tries to get rid of that on various places, eg: https://github.com/ngs-doo/dsl-json/blob/master/library/src/main/java/com/dslplatform/json/processor/Analysis.java#L1902
but its doing that in kind of hacky way due to Java6 not being aware of it. We can certainly look improving that if it resolves your use case (but I would first like to see it for myself).

unwx commented

I just checked this problem on other SDKs - everything is ok. The error occurs with Oracle OpenJDK 19, 19.0.1, with project Java version 16+

There were no errors with Oracle OpenJDK 17.0.3, 18.0.2 :0

Project: https://github.com/unwx/dsl-json-issue-246

zapov commented

Fixed in v2