Skip to content

Commit b31da0c

Browse files
committed
[Avro] Add support for @union deserialization
1 parent 575e4e2 commit b31da0c

30 files changed

+753
-224
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroAnnotationIntrospector.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@
77
import org.apache.avro.reflect.*;
88

99
import com.fasterxml.jackson.annotation.JsonCreator;
10+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
1011
import com.fasterxml.jackson.core.Version;
1112
import com.fasterxml.jackson.databind.AnnotationIntrospector;
13+
import com.fasterxml.jackson.databind.JavaType;
1214
import com.fasterxml.jackson.databind.PropertyName;
1315
import com.fasterxml.jackson.databind.cfg.MapperConfig;
1416
import com.fasterxml.jackson.databind.introspect.Annotated;
1517
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
1618
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
1719
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
20+
import com.fasterxml.jackson.databind.jsontype.NamedType;
21+
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
1822
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
1923
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper;
24+
2025
/**
2126
* Adds support for the following annotations from the Apache Avro implementation:
2227
* <ul>
@@ -73,7 +78,7 @@ public List<PropertyName> findPropertyAliases(Annotated m) {
7378
}
7479

7580
protected PropertyName _findName(Annotated a)
76-
{
81+
{
7782
AvroName ann = _findAnnotation(a, AvroName.class);
7883
return (ann == null) ? null : PropertyName.construct(ann.value());
7984
}
@@ -123,4 +128,28 @@ public List<NamedType> findSubtypes(Annotated a) {
123128
}
124129
return names;
125130
}
131+
132+
@Override
133+
public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config, AnnotatedClass ac, JavaType baseType) {
134+
return _findTypeResolver(config, ac, baseType);
135+
}
136+
137+
@Override
138+
public TypeResolverBuilder<?> findPropertyTypeResolver(MapperConfig<?> config, AnnotatedMember am, JavaType baseType) {
139+
return _findTypeResolver(config, am, baseType);
140+
}
141+
142+
@Override
143+
public TypeResolverBuilder<?> findPropertyContentTypeResolver(MapperConfig<?> config, AnnotatedMember am, JavaType containerType) {
144+
return _findTypeResolver(config, am, containerType);
145+
}
146+
147+
protected TypeResolverBuilder<?> _findTypeResolver(MapperConfig<?> config, Annotated ann, JavaType baseType) {
148+
TypeResolverBuilder<?> resolver = new AvroTypeResolverBuilder();
149+
JsonTypeInfo typeInfo = ann.getAnnotation(JsonTypeInfo.class);
150+
if (typeInfo != null && typeInfo.defaultImpl() != JsonTypeInfo.class) {
151+
resolver = resolver.defaultImpl(typeInfo.defaultImpl());
152+
}
153+
return resolver;
154+
}
126155
}

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroMapper.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package com.fasterxml.jackson.dataformat.avro;
22

3-
import java.io.*;
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.InputStream;
46

57
import org.apache.avro.Schema;
68

79
import com.fasterxml.jackson.core.Version;
8-
910
import com.fasterxml.jackson.databind.JavaType;
1011
import com.fasterxml.jackson.databind.JsonMappingException;
1112
import com.fasterxml.jackson.databind.Module;
1213
import com.fasterxml.jackson.databind.ObjectMapper;
13-
1414
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator;
1515

1616
/**

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroModule.java

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public AvroModule()
3737
addSerializer(File.class, new ToStringSerializer(File.class));
3838
// 08-Mar-2016, tatu: to fix [dataformat-avro#35], need to prune 'schema' property:
3939
setSerializerModifier(new AvroSerializerModifier());
40+
// Override untyped deserializer to one that checks for type information in the schema before going to default handling
41+
addDeserializer(Object.class, new AvroUntypedDeserializer());
4042
}
4143

4244
@Override

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/AvroParser.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.fasterxml.jackson.dataformat.avro;
22

33
import java.io.IOException;
4-
import java.io.InputStream;
54
import java.io.Writer;
65
import java.math.BigDecimal;
76

@@ -229,6 +228,16 @@ public void setSchema(FormatSchema schema)
229228

230229
protected abstract void _initSchema(AvroSchema schema) throws JsonProcessingException;
231230

231+
@Override
232+
public boolean canReadTypeId() {
233+
return true;
234+
}
235+
236+
@Override
237+
public Object getTypeId() throws IOException {
238+
return _avroContext != null ? _avroContext.getTypeId() : null;
239+
}
240+
232241
/*
233242
/**********************************************************
234243
/* Location info
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.fasterxml.jackson.dataformat.avro;
2+
3+
import java.io.IOException;
4+
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
import com.fasterxml.jackson.core.JsonParser;
7+
import com.fasterxml.jackson.databind.BeanProperty;
8+
import com.fasterxml.jackson.databind.DeserializationContext;
9+
import com.fasterxml.jackson.databind.JavaType;
10+
import com.fasterxml.jackson.databind.JsonDeserializer;
11+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
12+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
13+
import com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase;
14+
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper;
15+
16+
public class AvroTypeDeserializer extends TypeDeserializerBase {
17+
18+
protected AvroTypeDeserializer(JavaType baseType, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible,
19+
JavaType defaultImpl) {
20+
super(baseType, idRes, typePropertyName, typeIdVisible, defaultImpl);
21+
}
22+
23+
protected AvroTypeDeserializer(TypeDeserializerBase src, BeanProperty property) {
24+
super(src, property);
25+
}
26+
27+
@Override
28+
public TypeDeserializer forProperty(BeanProperty prop) {
29+
return new AvroTypeDeserializer(this, prop);
30+
}
31+
32+
@Override
33+
public JsonTypeInfo.As getTypeInclusion() {
34+
// Don't do any restructuring of the incoming JSON tokens
35+
return JsonTypeInfo.As.EXISTING_PROPERTY;
36+
}
37+
38+
@Override
39+
public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
40+
return deserializeTypedFromAny(p, ctxt);
41+
}
42+
43+
@Override
44+
public Object deserializeTypedFromArray(JsonParser p, DeserializationContext ctxt) throws IOException {
45+
return deserializeTypedFromAny(p, ctxt);
46+
}
47+
48+
@Override
49+
public Object deserializeTypedFromScalar(JsonParser p, DeserializationContext ctxt) throws IOException {
50+
return deserializeTypedFromAny(p, ctxt);
51+
}
52+
53+
@Override
54+
public Object deserializeTypedFromAny(JsonParser p, DeserializationContext ctxt) throws IOException {
55+
if (p.getTypeId() == null && getDefaultImpl() == null) {
56+
JsonDeserializer<Object> deser = _findDeserializer(ctxt, AvroSchemaHelper.getTypeId(_baseType));
57+
if (deser == null) {
58+
ctxt.reportInputMismatch(_baseType, "No (native) type id found when one was expected for polymorphic type handling");
59+
return null;
60+
}
61+
return deser.deserialize(p, ctxt);
62+
}
63+
return _deserializeWithNativeTypeId(p, ctxt, p.getTypeId());
64+
}
65+
66+
@Override
67+
protected JavaType _handleUnknownTypeId(DeserializationContext ctxt, String typeId)
68+
throws IOException {
69+
if (ctxt.hasValueDeserializerFor(_baseType, null)) {
70+
return _baseType;
71+
}
72+
return super._handleUnknownTypeId(ctxt, typeId);
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.fasterxml.jackson.dataformat.avro;
2+
3+
import java.io.IOException;
4+
import java.util.Collection;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import com.fasterxml.jackson.databind.DatabindContext;
9+
import com.fasterxml.jackson.databind.JavaType;
10+
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
11+
import com.fasterxml.jackson.databind.jsontype.NamedType;
12+
import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver;
13+
import com.fasterxml.jackson.databind.type.TypeFactory;
14+
15+
/**
16+
* {@link com.fasterxml.jackson.databind.jsontype.TypeIdResolver} for Avro type IDs embedded in schemas. Avro generally uses class names,
17+
* but we want to also support named subtypes so that developers can easily remap the embedded type IDs to a different runtime class.
18+
*/
19+
public class AvroTypeIdResolver extends ClassNameIdResolver {
20+
21+
private final Map<String, Class<?>> _idTypes = new HashMap<>();
22+
23+
private final Map<Class<?>, String> _typeIds = new HashMap<>();
24+
25+
public AvroTypeIdResolver(JavaType baseType, TypeFactory typeFactory, Collection<NamedType> subTypes) {
26+
this(baseType, typeFactory);
27+
if (subTypes != null) {
28+
for (NamedType namedType : subTypes) {
29+
registerSubtype(namedType.getType(), namedType.getName());
30+
}
31+
}
32+
}
33+
34+
public AvroTypeIdResolver(JavaType baseType, TypeFactory typeFactory) {
35+
super(baseType, typeFactory);
36+
}
37+
38+
@Override
39+
public void registerSubtype(Class<?> type, String name) {
40+
_idTypes.put(name, type);
41+
_typeIds.put(type, name);
42+
}
43+
44+
@Override
45+
protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException {
46+
// base types don't have subclasses
47+
if (_baseType.isPrimitive()) {
48+
return _baseType;
49+
}
50+
// check if there's a specific type we should be using for this ID
51+
Class<?> subType = _idTypes.get(id);
52+
if (subType != null) {
53+
id = _idFrom(null, subType, _typeFactory);
54+
}
55+
try {
56+
return super._typeFromId(id, ctxt);
57+
} catch (InvalidTypeIdException | IllegalArgumentException e) {
58+
// AvroTypeDeserializer expects null if we can't map the type ID to a class; It will throw an appropriate error if we can't
59+
// find a usable type.
60+
return null;
61+
}
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.fasterxml.jackson.dataformat.avro;
2+
3+
import java.util.Collection;
4+
5+
import com.fasterxml.jackson.databind.DeserializationConfig;
6+
import com.fasterxml.jackson.databind.JavaType;
7+
import com.fasterxml.jackson.databind.SerializationConfig;
8+
import com.fasterxml.jackson.databind.cfg.MapperConfig;
9+
import com.fasterxml.jackson.databind.jsontype.NamedType;
10+
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
11+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
12+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
13+
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
14+
15+
16+
public class AvroTypeResolverBuilder extends StdTypeResolverBuilder {
17+
18+
public AvroTypeResolverBuilder() {
19+
super();
20+
typeIdVisibility(false).typeProperty("@class");
21+
}
22+
23+
@Override
24+
public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
25+
// All type information is encoded in the schema, never in the data.
26+
return null;
27+
}
28+
29+
@Override
30+
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
31+
JavaType defaultImpl = null;
32+
if (getDefaultImpl() != null) {
33+
defaultImpl = config.constructType(getDefaultImpl());
34+
}
35+
36+
return new AvroTypeDeserializer(baseType,
37+
idResolver(config, baseType, subtypes, true, true),
38+
getTypeProperty(),
39+
isTypeIdVisible(),
40+
defaultImpl
41+
);
42+
43+
}
44+
45+
@Override
46+
protected TypeIdResolver idResolver(MapperConfig<?> config, JavaType baseType, Collection<NamedType> subtypes, boolean forSer,
47+
boolean forDeser) {
48+
return new AvroTypeIdResolver(baseType, config.getTypeFactory(), subtypes);
49+
}
50+
}

0 commit comments

Comments
 (0)