package com.fasterxml.jackson.dataformat.avro.deser;

import java.io.IOException;
import java.util.LinkedHashMap;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;

Deserializer for handling when we have no target type, just schema type. Normally, the UntypedObjectDeserializer doesn't look for native type information when handling scalar values, but Avro sometimes includes type information in the schema for scalar values; This subclass checks for the presence of valid type information and calls out to the type deserializer even for scalar values. The same goes for map keys.
Since:2.9
/** * Deserializer for handling when we have no target type, just schema type. Normally, the {@link UntypedObjectDeserializer} doesn't look * for native type information when handling scalar values, but Avro sometimes includes type information in the schema for scalar values; * This subclass checks for the presence of valid type information and calls out to the type deserializer even for scalar values. The * same goes for map keys. * * @since 2.9 */
public class AvroUntypedDeserializer extends UntypedObjectDeserializer implements ResolvableDeserializer { private static final long serialVersionUID = 1L; protected JavaType _typeObject; protected TypeDeserializer _typeDeserializer; public AvroUntypedDeserializer() { super(); } @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { _typeObject = ctxt.constructType(Object.class); _typeDeserializer = ctxt.getConfig().findTypeDeserializer(_typeObject); } @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // Make sure we have a native type ID *AND* that we can resolve it to a type; otherwise, we'll end up in a recursive loop if (p.canReadTypeId()) { Object typeId = p.getTypeId(); if (typeId != null) { if (_typeDeserializer != null) { TypeIdResolver resolver = _typeDeserializer.getTypeIdResolver(); if (resolver != null && resolver.typeFromId(ctxt, typeId.toString()) != null) { return _typeDeserializer.deserializeTypedFromAny(p, ctxt); } } } } return super.deserialize(p, ctxt); } @Override public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { // Use type deserializer if we have type information, even for scalar values if (p.canReadTypeId() && p.getTypeId() != null && typeDeserializer != null) { TypeIdResolver resolver = typeDeserializer.getTypeIdResolver(); // Make sure that we actually can resolve the type ID, otherwise we'll end up in a recursive loop if (resolver != null && resolver.typeFromId(ctxt, p.getTypeId().toString()) != null) { return typeDeserializer.deserializeTypedFromAny(p, ctxt); } } return super.deserializeWithType(p, ctxt, typeDeserializer); }
Method called to map a JSON Object into a Java value.
/** * Method called to map a JSON Object into a Java value. */
// Would we just be better off deferring to the Map<Object,Object> deserializer? @Override protected Object mapObject(JsonParser p, DeserializationContext ctxt) throws IOException { Object key1; JsonToken t = p.getCurrentToken(); if (t == JsonToken.START_OBJECT) { key1 = p.nextFieldName(); } else if (t == JsonToken.FIELD_NAME) { key1 = p.getCurrentName(); } else { if (t != JsonToken.END_OBJECT) { return ctxt.handleUnexpectedToken(handledType(), p); } key1 = null; } if (key1 == null) { // empty map might work; but caller may want to modify... so better just give small modifiable return new LinkedHashMap<>(2); } KeyDeserializer deserializer = null; if (p.getTypeId() != null) { if (_typeDeserializer != null) { TypeIdResolver idResolver = _typeDeserializer.getTypeIdResolver(); JavaType keyType = idResolver.typeFromId(ctxt, p.getTypeId().toString()); if (keyType != null) { deserializer = ctxt.findKeyDeserializer(keyType, null); } } } if (deserializer != null) { key1 = deserializer.deserializeKey(key1.toString(), ctxt); } p.nextToken(); Object value1 = deserialize(p, ctxt); Object key2 = p.nextFieldName(); if (key2 == null) { // has to be END_OBJECT, then // single entry; but we want modifiable LinkedHashMap<Object, Object> result = new LinkedHashMap<>(2); result.put(key1, value1); return result; } if (deserializer != null) { key2 = deserializer.deserializeKey(key2.toString(), ctxt); } p.nextToken(); Object value2 = deserialize(p, ctxt); Object key = p.nextFieldName(); if (key == null) { LinkedHashMap<Object, Object> result = new LinkedHashMap<>(4); result.put(key1, value1); result.put(key2, value2); return result; } // And then the general case; default map size is 16 LinkedHashMap<Object, Object> result = new LinkedHashMap<>(); result.put(key1, value1); result.put(key2, value2); do { if (deserializer != null) { key = deserializer.deserializeKey(key.toString(), ctxt); } p.nextToken(); result.put(key, deserialize(p, ctxt)); } while ((key = p.nextFieldName()) != null); return result; } }