Skip to content

Commit

Permalink
Tagged unions generated for EXISTING_PROPERTY discriminant (#582)
Browse files Browse the repository at this point in the history
  • Loading branch information
vojtechhabarta authored Nov 29, 2020
1 parent fce528d commit a52b4af
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
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;
}

}

0 comments on commit a52b4af

Please sign in to comment.