下面列出了怎么用com.fasterxml.jackson.annotation.JsonTypeName的API类实例代码及写法,或者点击链接到github查看源代码。
/**
* Uses reflection to extract options based on the fields of the provided config class
* ("List extensions" field is ignored, pending removal, Char is turned into String)
* The class must be annotated with {@code @JsonTypeName("type name")}
* @param pluginConfigClass the config class we want to extract options from through reflection
*/
FormatPluginOptionsDescriptor(Class<? extends FormatPluginConfig> pluginConfigClass) {
this.pluginConfigClass = pluginConfigClass;
Map<String, TableParamDef> paramsByName = new LinkedHashMap<>();
Field[] fields = pluginConfigClass.getDeclaredFields();
// @JsonTypeName("text")
JsonTypeName annotation = pluginConfigClass.getAnnotation(JsonTypeName.class);
this.typeName = annotation != null ? annotation.value() : null;
if (this.typeName != null) {
paramsByName.put("type", new TableParamDef("type", String.class));
}
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers())
// we want to deprecate this field
|| (field.getName().equals("extensions") && field.getType() == List.class)) {
continue;
}
Class<?> fieldType = field.getType();
if (fieldType == char.class) {
// calcite does not like char type. Just use String and enforce later that length == 1
fieldType = String.class;
}
paramsByName.put(field.getName(), new TableParamDef(field.getName(), fieldType).optional());
}
this.functionParamsByName = unmodifiableMap(paramsByName);
}
/**
* Determine the appropriate type identifier according to {@link JsonTypeInfo#use()}.
*
* @param javaType specific subtype to identify
* @param typeInfoAnnotation annotation for determining what kind of identifier to use
* @return type identifier (or {@code null} if no supported value could be found)
*/
private String getTypeIdentifier(ResolvedType javaType, JsonTypeInfo typeInfoAnnotation) {
Class<?> erasedTargetType = javaType.getErasedType();
final String typeIdentifier;
switch (typeInfoAnnotation.use()) {
case NAME:
typeIdentifier = Optional.ofNullable(erasedTargetType.getAnnotation(JsonTypeName.class))
.map(JsonTypeName::value)
.filter(name -> !name.isEmpty())
.orElseGet(() -> getUnqualifiedClassName(erasedTargetType));
break;
case CLASS:
typeIdentifier = erasedTargetType.getName();
break;
default:
typeIdentifier = null;
}
return typeIdentifier;
}
/**
* Uses reflection to extract options based on the fields of the provided config class
* ("List extensions" field is ignored, pending removal, Char is turned into String)
* The class must be annotated with {@code @JsonTypeName("type name")}
* @param pluginConfigClass the config class we want to extract options from through reflection
*/
FormatPluginOptionsDescriptor(Class<? extends FormatPluginConfig> pluginConfigClass) {
this.pluginConfigClass = pluginConfigClass;
Map<String, TableParamDef> paramsByName = new LinkedHashMap<>();
Field[] fields = pluginConfigClass.getDeclaredFields();
// @JsonTypeName("text")
JsonTypeName annotation = pluginConfigClass.getAnnotation(JsonTypeName.class);
this.typeName = annotation != null ? annotation.value() : null;
if (this.typeName != null) {
paramsByName.put("type", new TableParamDef("type", String.class));
}
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers())
// we want to deprecate this field
|| (field.getName().equals("extensions") && field.getType() == List.class)) {
continue;
}
Class<?> fieldType = field.getType();
if (fieldType == char.class) {
// calcite does not like char type. Just use String and enforce later that length == 1
fieldType = String.class;
}
paramsByName.put(field.getName(), new TableParamDef(field.getName(), fieldType).optional());
}
this.functionParamsByName = unmodifiableMap(paramsByName);
}
protected void testRoot() {
// while boundaries are compiler-checked, let's still verify superclass, as generics in Java are easy to bypass
assertTrue("Invalid root type: " + expectedRoot, PolymorphicConfiguration.class.isAssignableFrom(expectedRoot));
JsonTypeInfo typeInfo = expectedRoot.getAnnotation(JsonTypeInfo.class);
// TODO: test "property" and "use" values of the annotation
assertNotNull("Root is not annotated with @JsonTypeInfo", typeInfo);
if (expectedDefault != null) {
assertTrue("Default type is not specified on root. Expected: " + expectedDefault.getName(),
hasDefault(typeInfo));
assertEquals("Expected and actual default types are not the same", expectedDefault,
typeInfo.defaultImpl());
} else {
assertFalse("Expected no default type, but @JsonTypeInfo sets it to " + typeInfo.defaultImpl().getName() + ".",
hasDefault(typeInfo));
}
if (isConcrete(expectedRoot)) {
JsonTypeName typeName = expectedRoot.getAnnotation(JsonTypeName.class);
assertNotNull("Concrete root configuration type must be annotated with @JsonTypeName: " + expectedRoot.getName(), typeName);
}
}
protected void testRoot() {
// while boundaries are compiler-checked, let's still verify superclass, as generics in Java are easy to bypass
assertTrue(PolymorphicConfiguration.class.isAssignableFrom(expectedRoot), "Invalid root type: " + expectedRoot);
JsonTypeInfo typeInfo = expectedRoot.getAnnotation(JsonTypeInfo.class);
// TODO: test "property" and "use" values of the annotation
assertNotNull(typeInfo,"Root is not annotated with @JsonTypeInfo");
if (expectedDefault != null) {
assertTrue(hasDefault(typeInfo),
"Default type is not specified on root. Expected: " + expectedDefault.getName());
assertEquals(expectedDefault, typeInfo.defaultImpl(),
"Expected and actual default types are not the same");
} else {
assertFalse(hasDefault(typeInfo),
"Expected no default type, but @JsonTypeInfo sets it to " + typeInfo.defaultImpl().getName() + ".");
}
if (isConcrete(expectedRoot)) {
JsonTypeName typeName = expectedRoot.getAnnotation(JsonTypeName.class);
assertNotNull(typeName,"Concrete root configuration type must be annotated with @JsonTypeName: " + expectedRoot.getName());
}
}
private static IdentitiesMetadata collectIdentityMetadata(AlchemyServiceConfiguration configuration) {
final IdentitiesMetadata metadata = new IdentitiesMetadata();
for (final Entry<Class<? extends Identity>, IdentityMapping> entry : configuration.getIdentities().entrySet()) {
final JsonTypeName typeName = entry.getValue().getDtoType().getAnnotation(JsonTypeName.class);
Preconditions.checkNotNull(
typeName,
"identity DTO %s must specify @%s annotation",
entry.getValue().getDtoType().getSimpleName(),
JsonTypeName.class.getSimpleName()
);
metadata.put(
typeName.value(),
new IdentityMetadata(
typeName.value(),
entry.getKey(),
entry.getValue().getDtoType(),
entry.getValue().getMapperType()
)
);
}
return metadata;
}
protected void testNonRoot(Class<? extends T> t) {
// while boundaries are compiler-checked, let's still verify superclass, as generics in Java are easy to bypass
assertTrue("Invalid type " + t.getName() + ". Must be a subclass of root type " + expectedRoot.getName(),
expectedRoot.isAssignableFrom(t));
assertTrue("Non-root configuration type must not be abstract: " + t.getName(), isConcrete(t));
// this check would prevent matching subclasses by class, but we discourage that anyways.. (otherwise FQN
// would have to be used in YAML)
JsonTypeName typeName = t.getAnnotation(JsonTypeName.class);
assertNotNull("Non-root configuration type must be annotated with @JsonTypeName: " + t.getName(), typeName);
}
protected void testNonRoot(Class<? extends T> t) {
// while boundaries are compiler-checked, let's still verify superclass, as generics in Java are easy to bypass
assertTrue(expectedRoot.isAssignableFrom(t),
"Invalid type " + t.getName() + ". Must be a subclass of root type " + expectedRoot.getName());
assertTrue(isConcrete(t), "Non-root configuration type must not be abstract: " + t.getName());
// this check would prevent matching subclasses by class, but we discourage that anyways.. (otherwise FQN
// would have to be used in YAML)
JsonTypeName typeName = t.getAnnotation(JsonTypeName.class);
assertNotNull(typeName,"Non-root configuration type must be annotated with @JsonTypeName: " + t.getName());
}
protected String extractTypeLabel(Class<?> type) {
// TODO: get rid of Jackson annotations dependency .. devise our own that reflect Bootique style of config factory
// subclassing...
JsonTypeName typeName = type.getAnnotation(JsonTypeName.class);
return typeName != null ? typeName.value() : null;
}
private <T extends Filter> String buildTypeId(final String id, final Class<T> type) {
final JsonTypeName annotation = type.getAnnotation(JsonTypeName.class);
if (annotation == null) {
return id;
}
return annotation.value();
}
@Override
public void emitElements(Writer writer, Settings settings, boolean exportKeyword, TsModel model) {
for (TsBeanModel tsBean : model.getBeans()) {
final Class<?> beanClass = tsBean.getOrigin();
if (beanClass != null) {
final JsonSubTypes jsonSubTypes = beanClass.getAnnotation(JsonSubTypes.class);
final JsonTypeInfo jsonTypeInfo = beanClass.getAnnotation(JsonTypeInfo.class);
if (jsonSubTypes != null && jsonTypeInfo != null && jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY) {
final String propertyName = jsonTypeInfo.property();
for (JsonSubTypes.Type subType : jsonSubTypes.value()) {
String propertyValue = null;
if (jsonTypeInfo.use() == JsonTypeInfo.Id.NAME) {
if (subType.name().equals("")) {
final JsonTypeName jsonTypeName = subType.value().getAnnotation(JsonTypeName.class);
if (jsonTypeName != null) {
propertyValue = jsonTypeName.value();
}
} else {
propertyValue = subType.name();
}
}
if (propertyValue != null) {
final String baseTypeName = tsBean.getName().getSimpleName();
final String subTypeName = findTypeName(subType.value(), model);
if (baseTypeName != null && subTypeName != null) {
writer.writeIndentedLine("");
emitTypeGuard(writer, settings, exportKeyword, baseTypeName, subTypeName, propertyName, propertyValue);
}
}
}
}
}
}
}
private NamedType[] findSubtypes(Class<?> clazz, String pkg) {
ClassPathScanningCandidateComponentProvider provider =
new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(clazz));
return provider.findCandidateComponents(pkg).stream()
.map(
bean -> {
Class<?> cls =
ClassUtils.resolveClassName(
bean.getBeanClassName(), ClassUtils.getDefaultClassLoader());
JsonTypeName nameAnnotation = cls.getAnnotation(JsonTypeName.class);
if (nameAnnotation == null || "".equals(nameAnnotation.value())) {
String message =
"Subtype " + cls.getSimpleName() + " does not have a JsonTypeName annotation";
if (strictSerialization) {
throw new InvalidSubtypeConfigurationException(message);
}
log.warn(message);
return null;
}
return new NamedType(cls, nameAnnotation.value());
})
.filter(Objects::nonNull)
.toArray(NamedType[]::new);
}
private static String extractTypeMetadata( TreeLogger logger, RebindConfiguration configuration, JClassType baseType, JClassType
subtype, JsonTypeInfo typeInfo, Optional<JsonSubTypes> propertySubTypes, Optional<JsonSubTypes> baseSubTypes,
ImmutableList<JClassType> allSubtypes ) throws UnableToCompleteException {
switch ( typeInfo.use() ) {
case NAME:
// we first look the name on JsonSubTypes annotations. Top ones override the bottom ones.
String name = findNameOnJsonSubTypes( baseType, subtype, allSubtypes, propertySubTypes, baseSubTypes );
if ( null != name && !"".equals( name ) ) {
return name;
}
// we look if the name is defined on the type with JsonTypeName
Optional<JsonTypeName> typeName = findFirstEncounteredAnnotationsOnAllHierarchy( configuration, subtype, JsonTypeName
.class );
if ( typeName.isPresent() && !Strings.isNullOrEmpty( typeName.get().value() ) ) {
return typeName.get().value();
}
// we use the default name (ie simple name of the class)
String simpleBinaryName = subtype.getQualifiedBinaryName();
int indexLastDot = simpleBinaryName.lastIndexOf( '.' );
if ( indexLastDot != -1 ) {
simpleBinaryName = simpleBinaryName.substring( indexLastDot + 1 );
}
return simpleBinaryName;
case MINIMAL_CLASS:
if ( !baseType.getPackage().isDefault() ) {
String basePackage = baseType.getPackage().getName();
if ( subtype.getQualifiedBinaryName().startsWith( basePackage + "." ) ) {
return subtype.getQualifiedBinaryName().substring( basePackage.length() );
}
}
case CLASS:
return subtype.getQualifiedBinaryName();
default:
logger.log( TreeLogger.Type.ERROR, "JsonTypeInfo.Id." + typeInfo.use() + " is not supported" );
throw new UnableToCompleteException();
}
}
public String getProvider() {
var typeName = this.getClass().getAnnotation(JsonTypeName.class);
return typeName != null ? typeName.value() : null;
}
@Override
public TypeSpec.Builder classCreated(ObjectPluginContext objectPluginContext, ObjectTypeDeclaration ramlType, TypeSpec.Builder typeSpec, EventType eventType) {
if ( eventType == EventType.IMPLEMENTATION) {
return typeSpec;
}
ObjectTypeDeclaration otr = ramlType;
if (otr.discriminator() != null && objectPluginContext.childClasses(otr.name()).size() > 0) {
typeSpec.addAnnotation(AnnotationSpec.builder(JsonTypeInfo.class)
.addMember("use", "$T.Id.NAME", JsonTypeInfo.class)
.addMember("include", "$T.As.EXISTING_PROPERTY", JsonTypeInfo.class)
.addMember("property", "$S", otr.discriminator()).build());
AnnotationSpec.Builder subTypes = AnnotationSpec.builder(JsonSubTypes.class);
for (CreationResult result : objectPluginContext.childClasses(ramlType.name())) {
subTypes.addMember(
"value",
"$L",
AnnotationSpec
.builder(JsonSubTypes.Type.class)
.addMember("value", "$L",
result.getJavaName(EventType.INTERFACE) + ".class").build());
}
subTypes.addMember(
"value",
"$L",
AnnotationSpec
.builder(JsonSubTypes.Type.class)
.addMember("value", "$L",
objectPluginContext.creationResult().getJavaName(EventType.INTERFACE) + ".class").build());
typeSpec.addAnnotation(subTypes.build());
}
if (otr.discriminatorValue() != null) {
typeSpec.addAnnotation(AnnotationSpec.builder(JsonTypeName.class)
.addMember("value", "$S", otr.discriminatorValue()).build());
}
if (!Annotations.ABSTRACT.get(otr)) {
typeSpec.addAnnotation(AnnotationSpec.builder(JsonDeserialize.class)
.addMember("as", "$T.class", objectPluginContext.creationResult().getJavaName(EventType.IMPLEMENTATION))
.build());
}
return typeSpec;
}
@Test
public void test() {
FormatPluginOptionExtractor e = new FormatPluginOptionExtractor(CLASSPATH_SCAN_RESULT);
Collection<FormatPluginOptionsDescriptor> options = e.getOptions();
for (FormatPluginOptionsDescriptor d : options) {
assertEquals(d.pluginConfigClass.getAnnotation(JsonTypeName.class).value(), d.typeName);
switch (d.typeName) {
case "text":
assertEquals(TextFormatConfig.class, d.pluginConfigClass);
assertEquals(
"(type: String, lineDelimiter: String, fieldDelimiter: String, quote: String, escape: String, " +
"comment: String, skipFirstLine: boolean, extractHeader: boolean, " +
"autoGenerateColumnNames: boolean, trimHeader: boolean, outputExtension: String)",
d.presentParams()
);
break;
case "named":
assertEquals(NamedFormatPluginConfig.class, d.pluginConfigClass);
assertEquals("(type: String, name: String)", d.presentParams());
break;
case "json":
assertEquals(d.typeName, "(type: String, outputExtension: String, prettyPrint: boolean)", d.presentParams());
break;
case "parquet":
assertEquals(d.typeName, "(type: String, autoCorrectCorruptDates: boolean, outputExtension: String)", d.presentParams());
break;
case "arrow":
assertEquals(d.typeName, "(type: String, outputExtension: String)", d.presentParams());
break;
case "sequencefile":
case "avro":
assertEquals(d.typeName, "(type: String)", d.presentParams());
break;
case "excel":
assertEquals(d.typeName, "(type: String, sheet: String, extractHeader: boolean, hasMergedCells: boolean, xls: boolean)",
d.presentParams());
break;
case "iceberg":
assertEquals(d.typeName, "(type: String, metaStoreType: IcebergMetaStoreType, dataFormatType: FileType, dataFormatConfig: FormatPluginConfig)",
d.presentParams());
break;
default:
fail("add validation for format plugin type " + d.typeName);
}
}
}
private String getTypeName(JsonTypeInfo parentJsonTypeInfo, final Class<?> cls) {
// Id.CLASS
if (parentJsonTypeInfo.use() == JsonTypeInfo.Id.CLASS) {
return cls.getName();
}
// find custom name registered with `registerSubtypes`
AnnotatedClass annotatedClass = AnnotatedClassResolver
.resolveWithoutSuperTypes(objectMapper.getSerializationConfig(), cls);
Collection<NamedType> subtypes = objectMapper.getSubtypeResolver()
.collectAndResolveSubtypesByClass(objectMapper.getSerializationConfig(),
annotatedClass);
if (subtypes.size() == 1) {
NamedType subtype = subtypes.iterator().next();
if (subtype.getName() != null) {
return subtype.getName();
}
}
// find @JsonTypeName recursively
final JsonTypeName jsonTypeName = getAnnotationRecursive(cls, JsonTypeName.class);
if (jsonTypeName != null && !jsonTypeName.value().isEmpty()) {
return jsonTypeName.value();
}
// find @JsonSubTypes.Type recursively
final JsonSubTypes jsonSubTypes = getAnnotationRecursive(cls, JsonSubTypes.class, new Predicate<JsonSubTypes>() {
@Override
public boolean test(JsonSubTypes types) {
return getJsonSubTypeForClass(types, cls) != null;
}
});
if (jsonSubTypes != null) {
final JsonSubTypes.Type jsonSubType = getJsonSubTypeForClass(jsonSubTypes, cls);
if (!jsonSubType.name().isEmpty()) {
return jsonSubType.name();
}
}
// use simplified class name if it's not an interface or abstract
if(!cls.isInterface() && !Modifier.isAbstract(cls.getModifiers())) {
return cls.getName().substring(cls.getName().lastIndexOf(".") + 1);
}
return null;
}
private void registerSubtypes(Reflections reflections,ObjectMapper objectMapper) {
Set<Class<?>> subTypes = reflections.getTypesAnnotatedWith(JsonTypeName.class);
objectMapper.registerSubtypes(subTypes.toArray(new Class<?>[0]));
}