Skip to content

Commit

Permalink
[Avro] Add support for @union deserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
baharclerode committed Mar 10, 2017
1 parent 575e4e2 commit b31da0c
Show file tree
Hide file tree
Showing 30 changed files with 753 additions and 224 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@
import org.apache.avro.reflect.*;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper;

/**
* Adds support for the following annotations from the Apache Avro implementation:
* <ul>
Expand Down Expand Up @@ -73,7 +78,7 @@ public List<PropertyName> findPropertyAliases(Annotated m) {
}

protected PropertyName _findName(Annotated a)
{
{
AvroName ann = _findAnnotation(a, AvroName.class);
return (ann == null) ? null : PropertyName.construct(ann.value());
}
Expand Down Expand Up @@ -123,4 +128,28 @@ public List<NamedType> findSubtypes(Annotated a) {
}
return names;
}

@Override
public TypeResolverBuilder<?> findTypeResolver(MapperConfig<?> config, AnnotatedClass ac, JavaType baseType) {
return _findTypeResolver(config, ac, baseType);
}

@Override
public TypeResolverBuilder<?> findPropertyTypeResolver(MapperConfig<?> config, AnnotatedMember am, JavaType baseType) {
return _findTypeResolver(config, am, baseType);
}

@Override
public TypeResolverBuilder<?> findPropertyContentTypeResolver(MapperConfig<?> config, AnnotatedMember am, JavaType containerType) {
return _findTypeResolver(config, am, containerType);
}

protected TypeResolverBuilder<?> _findTypeResolver(MapperConfig<?> config, Annotated ann, JavaType baseType) {
TypeResolverBuilder<?> resolver = new AvroTypeResolverBuilder();
JsonTypeInfo typeInfo = ann.getAnnotation(JsonTypeInfo.class);
if (typeInfo != null && typeInfo.defaultImpl() != JsonTypeInfo.class) {
resolver = resolver.defaultImpl(typeInfo.defaultImpl());
}
return resolver;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.fasterxml.jackson.dataformat.avro;

import java.io.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import org.apache.avro.Schema;

import com.fasterxml.jackson.core.Version;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator;

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

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.dataformat.avro;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.math.BigDecimal;

Expand Down Expand Up @@ -229,6 +228,16 @@ public void setSchema(FormatSchema schema)

protected abstract void _initSchema(AvroSchema schema) throws JsonProcessingException;

@Override
public boolean canReadTypeId() {
return true;
}

@Override
public Object getTypeId() throws IOException {
return _avroContext != null ? _avroContext.getTypeId() : null;
}

/*
/**********************************************************
/* Location info
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.fasterxml.jackson.dataformat.avro;

import java.io.IOException;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase;
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaHelper;

public class AvroTypeDeserializer extends TypeDeserializerBase {

protected AvroTypeDeserializer(JavaType baseType, TypeIdResolver idRes, String typePropertyName, boolean typeIdVisible,
JavaType defaultImpl) {
super(baseType, idRes, typePropertyName, typeIdVisible, defaultImpl);
}

protected AvroTypeDeserializer(TypeDeserializerBase src, BeanProperty property) {
super(src, property);
}

@Override
public TypeDeserializer forProperty(BeanProperty prop) {
return new AvroTypeDeserializer(this, prop);
}

@Override
public JsonTypeInfo.As getTypeInclusion() {
// Don't do any restructuring of the incoming JSON tokens
return JsonTypeInfo.As.EXISTING_PROPERTY;
}

@Override
public Object deserializeTypedFromObject(JsonParser p, DeserializationContext ctxt) throws IOException {
return deserializeTypedFromAny(p, ctxt);
}

@Override
public Object deserializeTypedFromArray(JsonParser p, DeserializationContext ctxt) throws IOException {
return deserializeTypedFromAny(p, ctxt);
}

@Override
public Object deserializeTypedFromScalar(JsonParser p, DeserializationContext ctxt) throws IOException {
return deserializeTypedFromAny(p, ctxt);
}

@Override
public Object deserializeTypedFromAny(JsonParser p, DeserializationContext ctxt) throws IOException {
if (p.getTypeId() == null && getDefaultImpl() == null) {
JsonDeserializer<Object> deser = _findDeserializer(ctxt, AvroSchemaHelper.getTypeId(_baseType));
if (deser == null) {
ctxt.reportInputMismatch(_baseType, "No (native) type id found when one was expected for polymorphic type handling");
return null;
}
return deser.deserialize(p, ctxt);
}
return _deserializeWithNativeTypeId(p, ctxt, p.getTypeId());
}

@Override
protected JavaType _handleUnknownTypeId(DeserializationContext ctxt, String typeId)
throws IOException {
if (ctxt.hasValueDeserializerFor(_baseType, null)) {
return _baseType;
}
return super._handleUnknownTypeId(ctxt, typeId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.fasterxml.jackson.dataformat.avro;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.databind.DatabindContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver;
import com.fasterxml.jackson.databind.type.TypeFactory;

/**
* {@link com.fasterxml.jackson.databind.jsontype.TypeIdResolver} for Avro type IDs embedded in schemas. Avro generally uses class names,
* but we want to also support named subtypes so that developers can easily remap the embedded type IDs to a different runtime class.
*/
public class AvroTypeIdResolver extends ClassNameIdResolver {

private final Map<String, Class<?>> _idTypes = new HashMap<>();

private final Map<Class<?>, String> _typeIds = new HashMap<>();

public AvroTypeIdResolver(JavaType baseType, TypeFactory typeFactory, Collection<NamedType> subTypes) {
this(baseType, typeFactory);
if (subTypes != null) {
for (NamedType namedType : subTypes) {
registerSubtype(namedType.getType(), namedType.getName());
}
}
}

public AvroTypeIdResolver(JavaType baseType, TypeFactory typeFactory) {
super(baseType, typeFactory);
}

@Override
public void registerSubtype(Class<?> type, String name) {
_idTypes.put(name, type);
_typeIds.put(type, name);
}

@Override
protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException {
// base types don't have subclasses
if (_baseType.isPrimitive()) {
return _baseType;
}
// check if there's a specific type we should be using for this ID
Class<?> subType = _idTypes.get(id);
if (subType != null) {
id = _idFrom(null, subType, _typeFactory);
}
try {
return super._typeFromId(id, ctxt);
} catch (InvalidTypeIdException | IllegalArgumentException e) {
// AvroTypeDeserializer expects null if we can't map the type ID to a class; It will throw an appropriate error if we can't
// find a usable type.
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.fasterxml.jackson.dataformat.avro;

import java.util.Collection;

import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;


public class AvroTypeResolverBuilder extends StdTypeResolverBuilder {

public AvroTypeResolverBuilder() {
super();
typeIdVisibility(false).typeProperty("@class");
}

@Override
public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
// All type information is encoded in the schema, never in the data.
return null;
}

@Override
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) {
JavaType defaultImpl = null;
if (getDefaultImpl() != null) {
defaultImpl = config.constructType(getDefaultImpl());
}

return new AvroTypeDeserializer(baseType,
idResolver(config, baseType, subtypes, true, true),
getTypeProperty(),
isTypeIdVisible(),
defaultImpl
);

}

@Override
protected TypeIdResolver idResolver(MapperConfig<?> config, JavaType baseType, Collection<NamedType> subtypes, boolean forSer,
boolean forDeser) {
return new AvroTypeIdResolver(baseType, config.getTypeFactory(), subtypes);
}
}
Loading

0 comments on commit b31da0c

Please sign in to comment.