Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tagged unions generated for Jackson @JsonTypeInfo with EXISTING_PROPERTY #582

Merged
merged 1 commit into from
Nov 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions typescript-generator-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,29 +283,43 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass, List<String> class
}

final String discriminantProperty;
final boolean syntheticDiscriminantProperty;
final String discriminantLiteral;

final JsonTypeInfo jsonTypeInfo = sourceClass.type.getAnnotation(JsonTypeInfo.class);
final JsonTypeInfo parentJsonTypeInfo;
if (isSupported(jsonTypeInfo)) {
// this is parent
discriminantProperty = getDiscriminantPropertyName(jsonTypeInfo);
syntheticDiscriminantProperty = isDiscriminantPropertySynthetic(jsonTypeInfo);
discriminantLiteral = isInterfaceOrAbstract(sourceClass.type) ? null : getTypeName(jsonTypeInfo, sourceClass.type);
} else if (isSupported(parentJsonTypeInfo = getAnnotationRecursive(sourceClass.type, JsonTypeInfo.class))) {
// this is child class
discriminantProperty = getDiscriminantPropertyName(parentJsonTypeInfo);
syntheticDiscriminantProperty = isDiscriminantPropertySynthetic(parentJsonTypeInfo);
discriminantLiteral = getTypeName(parentJsonTypeInfo, sourceClass.type);
} else {
// not part of explicit hierarchy
discriminantProperty = null;
syntheticDiscriminantProperty = false;
discriminantLiteral = null;
}

if (discriminantProperty != null && properties.stream().anyMatch(property -> Objects.equals(property.getName(), discriminantProperty))) {
TypeScriptGenerator.getLogger().warning(String.format(
"Class '%s' has duplicate property '%s'. "
+ "For more information see 'https://github.com/vojtechhabarta/typescript-generator/issues/392'.",
sourceClass.type.getName(), discriminantProperty));
if (discriminantProperty != null) {
final PropertyModel foundDiscriminantProperty = properties.stream()
.filter(property -> Objects.equals(property.getName(), discriminantProperty))
.findFirst()
.orElse(null);
if (foundDiscriminantProperty != null) {
if (syntheticDiscriminantProperty) {
TypeScriptGenerator.getLogger().warning(String.format(
"Class '%s' has duplicate property '%s'. "
+ "For more information see 'https://github.com/vojtechhabarta/typescript-generator/issues/392'.",
sourceClass.type.getName(), discriminantProperty));
} else {
properties.remove(foundDiscriminantProperty);
}
}
}

final List<Class<?>> taggedUnionClasses;
Expand Down Expand Up @@ -387,10 +401,14 @@ private Type processIdentity(Type propertyType, BeanProperty beanProperty) {

private static boolean isSupported(JsonTypeInfo jsonTypeInfo) {
return jsonTypeInfo != null &&
jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY &&
(jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY || jsonTypeInfo.include() == JsonTypeInfo.As.EXISTING_PROPERTY) &&
(jsonTypeInfo.use() == JsonTypeInfo.Id.NAME || jsonTypeInfo.use() == JsonTypeInfo.Id.CLASS);
}

private boolean isDiscriminantPropertySynthetic(JsonTypeInfo jsonTypeInfo) {
return jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY;
}

private String getDiscriminantPropertyName(JsonTypeInfo jsonTypeInfo) {
return jsonTypeInfo.property().isEmpty()
? jsonTypeInfo.use().getDefaultPropertyName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import javax.xml.bind.JAXBElement;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Assert;
Expand Down Expand Up @@ -691,7 +692,7 @@ public static interface AccountResource extends AbstractCrudResource<AccountDto,
}

public static void main(String[] args) {
final ResourceConfig config = new ResourceConfig(BeanParamResource.class);
final ResourceConfig config = new ResourceConfig(BeanParamResource.class, JacksonFeature.class);
JdkHttpServerFactory.createHttpServer(URI.create("http://localhost:9998/"), config);
System.out.println("Jersey started.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

package cz.habarta.typescript.generator;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
Expand Down Expand Up @@ -493,4 +494,79 @@ public void testGenericBaseWithNonGenericSubType() {
Assert.assertTrue(output.contains("type EntityUnion<T> = cz.habarta.typescript.generator.TaggedUnionsTest.Foo | cz.habarta.typescript.generator.TaggedUnionsTest.Bar"));
}

@Test
public void testTaggedUnionsWithExistingProperty() {
final Settings settings = TestUtils.settings();
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Geometry2.class));
final String expected = (
"\n" +
"interface Geometry2 {\n" +
" shapes: Shape2Union[];\n" +
"}\n" +
"\n" +
"interface Shape2 {\n" +
" kind: 'square' | 'rectangle' | 'circle';\n" +
"}\n" +
"\n" +
"interface Square2 extends Shape2 {\n" +
" kind: 'square';\n" +
" size: number;\n" +
"}\n" +
"\n" +
"interface Rectangle2 extends Shape2 {\n" +
" kind: 'rectangle';\n" +
" width: number;\n" +
" height: number;\n" +
"}\n" +
"\n" +
"interface Circle2 extends Shape2 {\n" +
" kind: 'circle';\n" +
" radius: number;\n" +
"}\n" +
"\n" +
"type Shape2Union = Square2 | Rectangle2 | Circle2;\n" +
""
).replace('\'', '"');
Assert.assertEquals(expected, output);
}

private static class Geometry2 {
public List<Shape2> shapes;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "kind")
@JsonSubTypes({
@JsonSubTypes.Type(Square2.class),
@JsonSubTypes.Type(Rectangle2.class),
@JsonSubTypes.Type(Circle2.class),
})
private abstract static class Shape2 {
@JsonProperty("kind")
private final String kind;

public Shape2() {
final JsonTypeName annotation = getClass().getAnnotation(JsonTypeName.class);
if (annotation == null) {
throw new RuntimeException("Annotation @JsonTypeName not specified on " + getClass());
}
this.kind = annotation.value();
}
}

@JsonTypeName("square")
private static class Square2 extends Shape2 {
public double size;
}

@JsonTypeName("rectangle")
private static class Rectangle2 extends Shape2 {
public double width;
public double height;
}

@JsonTypeName("circle")
private static class Circle2 extends Shape2 {
public double radius;
}

}