Skip to content

Commit 36696b7

Browse files
Getting type names for tagged unions directly from Jackson2 (#635)
(using `TypeSerializer` and `SubtypeResolver`)
1 parent 580e37b commit 36696b7

File tree

1 file changed

+43
-47
lines changed
  • typescript-generator-core/src/main/java/cz/habarta/typescript/generator/parser

1 file changed

+43
-47
lines changed

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

Lines changed: 43 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
1010
import com.fasterxml.jackson.annotation.JsonSubTypes;
1111
import com.fasterxml.jackson.annotation.JsonTypeInfo;
12-
import com.fasterxml.jackson.annotation.JsonTypeName;
1312
import com.fasterxml.jackson.annotation.JsonUnwrapped;
1413
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
1514
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -36,8 +35,10 @@
3635
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
3736
import com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap;
3837
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
39-
import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
4038
import com.fasterxml.jackson.databind.jsontype.NamedType;
39+
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
40+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
41+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
4142
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
4243
import com.fasterxml.jackson.databind.ser.BeanSerializer;
4344
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
@@ -67,6 +68,7 @@
6768
import java.util.Collection;
6869
import java.util.Collections;
6970
import java.util.Comparator;
71+
import java.util.LinkedHashSet;
7072
import java.util.List;
7173
import java.util.Map;
7274
import java.util.Objects;
@@ -297,13 +299,13 @@ private BeanModel parseBean(SourceType<Class<?>> sourceClass, List<String> class
297299
final JsonTypeInfo jsonTypeInfo = classWithJsonTypeInfo.getValue2();
298300
discriminantProperty = getDiscriminantPropertyName(jsonTypeInfo);
299301
syntheticDiscriminantProperty = isDiscriminantPropertySynthetic(jsonTypeInfo);
300-
discriminantLiteral = isInterfaceOrAbstract(sourceClass.type) ? null : getTypeName(jsonTypeInfo, sourceClass.type);
302+
discriminantLiteral = isInterfaceOrAbstract(sourceClass.type) ? null : getTypeName(sourceClass.type);
301303
} else if (isTaggedUnion(parentClassWithJsonTypeInfo = getAnnotationRecursive(sourceClass.type, JsonTypeInfo.class))) {
302304
// this is child class
303305
final JsonTypeInfo parentJsonTypeInfo = parentClassWithJsonTypeInfo.getValue2();
304306
discriminantProperty = getDiscriminantPropertyName(parentJsonTypeInfo);
305307
syntheticDiscriminantProperty = isDiscriminantPropertySynthetic(parentJsonTypeInfo);
306-
discriminantLiteral = getTypeName(parentJsonTypeInfo, sourceClass.type);
308+
discriminantLiteral = getTypeName(sourceClass.type);
307309
} else {
308310
// not part of explicit hierarchy
309311
discriminantProperty = null;
@@ -435,59 +437,53 @@ private String getDiscriminantPropertyName(JsonTypeInfo jsonTypeInfo) {
435437
: jsonTypeInfo.property();
436438
}
437439

438-
private String getTypeName(JsonTypeInfo parentJsonTypeInfo, final Class<?> cls) {
439-
// Id.CLASS
440-
if (parentJsonTypeInfo.use() == JsonTypeInfo.Id.CLASS) {
441-
return cls.getName();
442-
}
443-
// find custom name registered with `registerSubtypes`
444-
AnnotatedClass annotatedClass = AnnotatedClassResolver
445-
.resolveWithoutSuperTypes(objectMapper.getSerializationConfig(), cls);
446-
Collection<NamedType> subtypes = objectMapper.getSubtypeResolver()
447-
.collectAndResolveSubtypesByClass(objectMapper.getSerializationConfig(),
448-
annotatedClass);
449-
450-
if (subtypes.size() == 1) {
451-
NamedType subtype = subtypes.iterator().next();
440+
private String getTypeName(Class<?> cls) {
441+
final List<String> typeNames = getTypeNamesOrEmptyOrNull(cls);
442+
return typeNames != null && !typeNames.isEmpty() ? typeNames.get(0) : null;
443+
}
452444

453-
if (subtype.getName() != null) {
454-
return subtype.getName();
445+
private List<String> getTypeNamesOrEmptyOrNull(Class<?> cls) {
446+
try {
447+
final SerializationConfig config = objectMapper.getSerializationConfig();
448+
final JavaType javaType = config.constructType(cls);
449+
final TypeSerializer typeSerializer = objectMapper.getSerializerProviderInstance().findTypeSerializer(javaType);
450+
final TypeIdResolver typeIdResolver = typeSerializer.getTypeIdResolver();
451+
if (typeIdResolver.getMechanism() == JsonTypeInfo.Id.NAME) {
452+
final SubtypeResolver subtypeResolver = config.getSubtypeResolver();
453+
final BeanDescription beanDescription = config.introspectClassAnnotations(cls);
454+
final AnnotatedClass annotatedClass = beanDescription.getClassInfo();
455+
final Collection<NamedType> serializationSubtypes = subtypeResolver.collectAndResolveSubtypesByClass(config, annotatedClass);
456+
final Collection<NamedType> deserializationSubtypes = subtypeResolver.collectAndResolveSubtypesByTypeId(config, annotatedClass);
457+
final List<String> serializationTypeNames = getTypeNamesFromSubtypes(serializationSubtypes, cls); // 0 or 1
458+
final List<String> deserializationTypeNames = getTypeNamesFromSubtypes(deserializationSubtypes, cls); // 0 or n
459+
final LinkedHashSet<String> typeNames = Stream
460+
.concat(serializationTypeNames.stream(), deserializationTypeNames.stream())
461+
.collect(Collectors.toCollection(LinkedHashSet::new));
462+
if (typeNames.isEmpty()) {
463+
return isInterfaceOrAbstract(cls) ? null : Utils.listFromNullable(typeIdResolver.idFromBaseType());
464+
} else {
465+
return new ArrayList<>(typeNames);
466+
}
467+
} else {
468+
return Utils.listFromNullable(typeIdResolver.idFromBaseType());
455469
}
470+
} catch (Exception e) {
471+
return null;
456472
}
473+
}
457474

458-
// find @JsonTypeName recursively
459-
final JsonTypeName jsonTypeName = getAnnotationRecursive(cls, JsonTypeName.class).getValue2();
460-
if (jsonTypeName != null && !jsonTypeName.value().isEmpty()) {
461-
return jsonTypeName.value();
462-
}
463-
// find @JsonSubTypes.Type recursively
464-
final JsonSubTypes jsonSubTypes = getAnnotationRecursive(cls, JsonSubTypes.class, (JsonSubTypes types) -> getJsonSubTypeForClass(types, cls) != null).getValue2();
465-
if (jsonSubTypes != null) {
466-
final JsonSubTypes.Type jsonSubType = getJsonSubTypeForClass(jsonSubTypes, cls);
467-
if (!jsonSubType.name().isEmpty()) {
468-
return jsonSubType.name();
469-
}
470-
}
471-
// use simplified class name if it's not an interface or abstract
472-
if(!isInterfaceOrAbstract(cls)) {
473-
return cls.getName().substring(cls.getName().lastIndexOf(".") + 1);
474-
}
475-
return null;
475+
private static List<String> getTypeNamesFromSubtypes(Collection<NamedType> subtypes, Class<?> cls) {
476+
return subtypes.stream()
477+
.filter(subtype -> Objects.equals(subtype.getType(), cls))
478+
.filter(NamedType::hasName)
479+
.map(NamedType::getName)
480+
.collect(Collectors.toList());
476481
}
477482

478483
private boolean isInterfaceOrAbstract(Class<?> cls) {
479484
return cls.isInterface() || Modifier.isAbstract(cls.getModifiers());
480485
}
481486

482-
private static JsonSubTypes.Type getJsonSubTypeForClass(JsonSubTypes types, Class<?> cls) {
483-
for (JsonSubTypes.Type type : types.value()) {
484-
if (type.value().equals(cls)) {
485-
return type;
486-
}
487-
}
488-
return null;
489-
}
490-
491487
private static <T extends Annotation> Pair<Class<?>, T> getAnnotationRecursive(Class<?> cls, Class<T> annotationClass) {
492488
return getAnnotationRecursive(cls, annotationClass, null);
493489
}

0 commit comments

Comments
 (0)