Skip to content

Commit 5beeb24

Browse files
Support for @JsonUnwrapped in Jackson2 (refs #122)
1 parent 082afd1 commit 5beeb24

File tree

7 files changed

+157
-18
lines changed

7 files changed

+157
-18
lines changed

typescript-generator-core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
<artifactId>jersey-media-json-jackson</artifactId>
6262
<version>${jersey.version}</version>
6363
<scope>test</scope>
64-
</dependency>
64+
</dependency>
6565
<dependency>
6666
<groupId>org.glassfish.jersey.containers</groupId>
6767
<artifactId>jersey-container-jdk-http</artifactId>

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ public TsModel javaToTypeScript(Model model) {
8484
}
8585

8686
public TsType javaToTypeScript(Type type) {
87-
final BeanModel beanModel = new BeanModel(Object.class, Object.class, null, null, null, Collections.<Type>emptyList(), Collections.singletonList(new PropertyModel("property", type, false, null, null)), null);
87+
final BeanModel beanModel = new BeanModel(Object.class, Object.class, null, null, null, Collections.<Type>emptyList(),
88+
Collections.singletonList(new PropertyModel("property", type, false, null, null, null)), null);
8889
final Model model = new Model(Collections.singletonList(beanModel), Collections.<EnumModel<?>>emptyList(), null);
8990
final TsModel tsModel = javaToTypeScript(model);
9091
return tsModel.getBeans().get(0).getProperties().get(0).getTsType();
@@ -94,7 +95,7 @@ private TsModel processModel(SymbolTable symbolTable, Model model) {
9495
final Map<Type, List<BeanModel>> children = createChildrenMap(model);
9596
final List<TsBeanModel> beans = new ArrayList<>();
9697
for (BeanModel bean : model.getBeans()) {
97-
beans.add(processBean(symbolTable, children, bean));
98+
beans.add(processBean(symbolTable, model, children, bean));
9899
}
99100
final List<TsEnumModel<?>> enums = new ArrayList<>();
100101
for (EnumModel<?> enumModel : model.getEnums()) {
@@ -117,7 +118,7 @@ private Map<Type, List<BeanModel>> createChildrenMap(Model model) {
117118
return children;
118119
}
119120

120-
private <T> TsBeanModel processBean(SymbolTable symbolTable, Map<Type, List<BeanModel>> children, BeanModel bean) {
121+
private <T> TsBeanModel processBean(SymbolTable symbolTable, Model model, Map<Type, List<BeanModel>> children, BeanModel bean) {
121122
final boolean isClass = !bean.getOrigin().isInterface() && settings.mapClasses == ClassMapping.asClasses;
122123
final Symbol beanIdentifier = symbolTable.getSymbol(bean.getOrigin());
123124
final List<TsType.GenericVariableType> typeParameters = new ArrayList<>();
@@ -135,10 +136,7 @@ private <T> TsBeanModel processBean(SymbolTable symbolTable, Map<Type, List<Bean
135136
interfaces.add(interfaceType);
136137
}
137138
}
138-
final List<TsPropertyModel> properties = new ArrayList<>();
139-
for (PropertyModel property : bean.getProperties()) {
140-
properties.add(processProperty(symbolTable, bean, property));
141-
}
139+
final List<TsPropertyModel> properties = processProperties(symbolTable, model, bean, "", "");
142140

143141
if (bean.getDiscriminantProperty() != null && !containsProperty(properties, bean.getDiscriminantProperty())) {
144142
final List<BeanModel> selfAndDescendants = getSelfAndDescendants(bean, children);
@@ -157,6 +155,27 @@ private <T> TsBeanModel processBean(SymbolTable symbolTable, Map<Type, List<Bean
157155
return new TsBeanModel(bean.getOrigin(), isClass, beanIdentifier, typeParameters, parentType, bean.getTaggedUnionClasses(), interfaces, properties, null, null, bean.getComments());
158156
}
159157

158+
private List<TsPropertyModel> processProperties(SymbolTable symbolTable, Model model, BeanModel bean, String prefix, String suffix) {
159+
final List<TsPropertyModel> properties = new ArrayList<>();
160+
for (PropertyModel property : bean.getProperties()) {
161+
boolean pulled = false;
162+
final PropertyModel.PullProperties pullProperties = property.getPullProperties();
163+
if (pullProperties != null) {
164+
if (property.getType() instanceof Class<?>) {
165+
final BeanModel pullBean = model.getBean((Class<?>) property.getType());
166+
if (pullBean != null) {
167+
properties.addAll(processProperties(symbolTable, model, pullBean, prefix + pullProperties.prefix, pullProperties.suffix + suffix));
168+
pulled = true;
169+
}
170+
}
171+
}
172+
if (!pulled) {
173+
properties.add(processProperty(symbolTable, bean, property, prefix, suffix));
174+
}
175+
}
176+
return properties;
177+
}
178+
160179
private static List<BeanModel> getSelfAndDescendants(BeanModel bean, Map<Type, List<BeanModel>> children) {
161180
final List<BeanModel> descendants = new ArrayList<>();
162181
descendants.add(bean);
@@ -178,10 +197,10 @@ private static boolean containsProperty(List<TsPropertyModel> properties, String
178197
return false;
179198
}
180199

181-
private TsPropertyModel processProperty(SymbolTable symbolTable, BeanModel bean, PropertyModel property) {
200+
private TsPropertyModel processProperty(SymbolTable symbolTable, BeanModel bean, PropertyModel property, String prefix, String suffix) {
182201
final TsType type = typeFromJava(symbolTable, property.getType(), property.getName(), bean.getOrigin());
183202
final TsType tsType = property.isOptional() ? type.optional() : type;
184-
return new TsPropertyModel(property.getName(), tsType, settings.declarePropertiesAsReadOnly, property.getComments());
203+
return new TsPropertyModel(prefix + property.getName() + suffix, tsType, settings.declarePropertiesAsReadOnly, property.getComments());
185204
}
186205

187206
private TsEnumModel<?> processEnum(SymbolTable symbolTable, EnumModel<?> enumModel) {

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/Jackson1Parser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@ protected BeanModel parseBean(SourceType<Class<?>> sourceClass) {
6565
}
6666
}
6767
final Member originalMember = beanPropertyWriter.getMember().getMember();
68-
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, optional, sourceClass.type, originalMember));
68+
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, optional, sourceClass.type, originalMember, null));
6969
}
7070
}
7171

7272
final JsonTypeInfo jsonTypeInfo = sourceClass.type.getAnnotation(JsonTypeInfo.class);
7373
if (jsonTypeInfo != null && jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY) {
7474
if (!containsProperty(properties, jsonTypeInfo.property())) {
75-
properties.add(new PropertyModel(jsonTypeInfo.property(), String.class, false, null, null));
75+
properties.add(new PropertyModel(jsonTypeInfo.property(), String.class, false, null, null, null));
7676
}
7777
}
7878

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/Jackson2Parser.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.annotation.JsonSubTypes;
77
import com.fasterxml.jackson.annotation.JsonTypeInfo;
88
import com.fasterxml.jackson.annotation.JsonTypeName;
9+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
910
import com.fasterxml.jackson.annotation.JsonValue;
1011
import com.fasterxml.jackson.databind.*;
1112
import com.fasterxml.jackson.databind.ser.*;
@@ -18,6 +19,7 @@
1819
import java.beans.Introspector;
1920
import java.beans.PropertyDescriptor;
2021
import java.lang.annotation.Annotation;
22+
import java.lang.reflect.AccessibleObject;
2123
import java.lang.reflect.Field;
2224
import java.lang.reflect.Member;
2325
import java.lang.reflect.Method;
@@ -77,8 +79,17 @@ protected BeanModel parseBean(SourceType<Class<?>> sourceClass) {
7779
break;
7880
}
7981
}
82+
// @JsonUnwrapped
83+
PropertyModel.PullProperties pullProperties = null;
8084
final Member originalMember = beanPropertyWriter.getMember().getMember();
81-
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, optional, sourceClass.type, originalMember));
85+
if (originalMember instanceof AccessibleObject) {
86+
final AccessibleObject accessibleObject = (AccessibleObject) originalMember;
87+
final JsonUnwrapped annotation = accessibleObject.getAnnotation(JsonUnwrapped.class);
88+
if (annotation != null && annotation.enabled()) {
89+
pullProperties = new PropertyModel.PullProperties(annotation.prefix(), annotation.suffix());
90+
}
91+
}
92+
properties.add(processTypeAndCreateProperty(beanPropertyWriter.getName(), propertyType, optional, sourceClass.type, originalMember, pullProperties));
8293
}
8394
}
8495

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/ModelParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,12 @@ protected void addBeanToQueue(SourceType<? extends Type> sourceType) {
9393
typeQueue.add(sourceType);
9494
}
9595

96-
protected PropertyModel processTypeAndCreateProperty(String name, Type type, boolean optional, Class<?> usedInClass, Member originalMember) {
96+
protected PropertyModel processTypeAndCreateProperty(String name, Type type, boolean optional, Class<?> usedInClass, Member originalMember, PropertyModel.PullProperties pullProperties) {
9797
List<Class<?>> classes = discoverClassesUsedInType(type);
9898
for (Class<?> cls : classes) {
9999
typeQueue.add(new SourceType<>(cls, usedInClass, name));
100100
}
101-
return new PropertyModel(name, type, optional, originalMember, null);
101+
return new PropertyModel(name, type, optional, originalMember, pullProperties, null);
102102
}
103103

104104
private List<Class<?>> discoverClassesUsedInType(Type type) {

typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser/PropertyModel.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,25 @@ public class PropertyModel {
1212
private final Type type;
1313
private final boolean optional;
1414
private final Member originalMember;
15+
private final PullProperties pullProperties;
1516
private final List<String> comments;
1617

17-
public PropertyModel(String name, Type type, boolean optional, Member originalMember, List<String> comments) {
18+
public static class PullProperties {
19+
public final String prefix;
20+
public final String suffix;
21+
22+
public PullProperties(String prefix, String suffix) {
23+
this.prefix = prefix;
24+
this.suffix = suffix;
25+
}
26+
}
27+
28+
public PropertyModel(String name, Type type, boolean optional, Member originalMember, PullProperties pullProperties, List<String> comments) {
1829
this.name = name;
1930
this.type = type;
2031
this.optional = optional;
2132
this.originalMember = originalMember;
33+
this.pullProperties = pullProperties;
2234
this.comments = comments;
2335
}
2436

@@ -39,15 +51,19 @@ public Member getOriginalMember() {
3951
}
4052

4153
public PropertyModel originalMember(Member originalMember) {
42-
return new PropertyModel(name, type, optional, originalMember, comments);
54+
return new PropertyModel(name, type, optional, originalMember, pullProperties, comments);
55+
}
56+
57+
public PullProperties getPullProperties() {
58+
return pullProperties;
4359
}
4460

4561
public List<String> getComments() {
4662
return comments;
4763
}
4864

4965
public PropertyModel withComments(List<String> comments) {
50-
return new PropertyModel(name, type, optional, originalMember, comments);
66+
return new PropertyModel(name, type, optional, originalMember, pullProperties, comments);
5167
}
5268

5369
@Override
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
2+
package cz.habarta.typescript.generator;
3+
4+
import com.fasterxml.jackson.annotation.JsonUnwrapped;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.databind.SerializationFeature;
7+
import cz.habarta.typescript.generator.util.StandardJsonPrettyPrinter;
8+
import org.junit.Assert;
9+
import org.junit.Test;
10+
11+
12+
public class JsonUnwrappedTest {
13+
14+
@Test
15+
public void test() {
16+
final Settings settings = TestUtils.settings();
17+
final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(Person.class));
18+
final String expected = "\n"
19+
+ "interface Person {\n"
20+
+ " AageA: number;\n"
21+
+ " AfirstA: string;\n"
22+
+ " AlastA: string;\n"
23+
+ " A_first2A: string;\n"
24+
+ " A_last2A: string;\n"
25+
+ " Aname3A: Name;\n"
26+
+ " BageB: number;\n"
27+
+ " BfirstB: string;\n"
28+
+ " BlastB: string;\n"
29+
+ " B_first2B: string;\n"
30+
+ " B_last2B: string;\n"
31+
+ " Bname3B: Name;\n"
32+
+ "}\n"
33+
+ "\n"
34+
+ "interface Parent {\n"
35+
+ " age: number;\n"
36+
+ " first: string;\n"
37+
+ " last: string;\n"
38+
+ " _first2: string;\n"
39+
+ " _last2: string;\n"
40+
+ " name3: Name;\n"
41+
+ "}\n"
42+
+ "\n"
43+
+ "interface Name {\n"
44+
+ " first: string;\n"
45+
+ " last: string;\n"
46+
+ "}\n"
47+
+ "";
48+
Assert.assertEquals(expected.trim(), output.trim());
49+
}
50+
51+
public static class Person {
52+
@JsonUnwrapped(prefix = "A", suffix = "A")
53+
public Parent parentA;
54+
@JsonUnwrapped(prefix = "B", suffix = "B")
55+
public Parent parentB;
56+
}
57+
58+
public static class Parent {
59+
public int age;
60+
@JsonUnwrapped
61+
public Name name;
62+
@JsonUnwrapped(prefix = "_", suffix = "2")
63+
public Name name2;
64+
@JsonUnwrapped(enabled = false)
65+
public Name name3;
66+
}
67+
68+
public static class Name {
69+
public String first, last;
70+
}
71+
72+
public static void main(String[] args) throws Exception {
73+
final Parent parent = new Parent();
74+
parent.age = 18;
75+
parent.name = new Name();
76+
parent.name.first = "Joey";
77+
parent.name.last = "Sixpack";
78+
parent.name2 = new Name();
79+
parent.name2.first = "Joey";
80+
parent.name2.last = "Sixpack";
81+
parent.name3 = new Name();
82+
parent.name3.first = "Joey";
83+
parent.name3.last = "Sixpack";
84+
final Person person = new Person();
85+
person.parentA = parent;
86+
person.parentB = parent;
87+
final ObjectMapper objectMapper = new ObjectMapper();
88+
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
89+
objectMapper.setDefaultPrettyPrinter(new StandardJsonPrettyPrinter());
90+
System.out.println(objectMapper.writeValueAsString(person));
91+
}
92+
93+
}

0 commit comments

Comments
 (0)