ProtoMapDeserializer.java

package net.morimekta.proto.jackson.adapter;

import com.fasterxml.jackson.core.JsonParseException;
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.JsonDeserializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import net.morimekta.collect.MapBuilder;
import net.morimekta.collect.UnmodifiableMap;
import net.morimekta.collect.UnmodifiableSortedMap;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Deserialize maps with proto enum keys.
 *
 * @param <K> The key type.
 * @param <V> The value type.
 */
public class ProtoMapDeserializer<K, V> extends JsonDeserializer<Map<K, V>> {
    private final Class<?>        mapType;
    private final KeyDeserializer keyDeserializer;
    private final JavaType        valueType;

    /**
     * Instantiate deserializer.
     *
     * @param mapType         The class for the map type.
     * @param keyDeserializer The key serializer.
     * @param valueType       The jackson value type definition.
     */
    public ProtoMapDeserializer(Class<?> mapType,
                                KeyDeserializer keyDeserializer,
                                JavaType valueType) {
        this.mapType = mapType;
        this.keyDeserializer = keyDeserializer;
        this.valueType = valueType;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map<K, V> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (p.currentToken() == JsonToken.VALUE_NULL) {
            return null;
        }
        if (p.currentToken() == JsonToken.START_OBJECT) {
            MapBuilder<K, V> builder = UnmodifiableMap.newBuilder();
            String next = p.nextFieldName();
            while (next != null) {
                K key = (K) keyDeserializer.deserializeKey(next, ctxt);
                p.nextValue();
                V value = ctxt.readValue(p, valueType);
                if (key != null && value != null) {
                    builder.put(key, value);
                }
                next = p.nextFieldName();
            }
            return mapWrapper(builder.build());
        } else {
            throw new JsonParseException(p, "Invalid map start");
        }
    }

    private Map<K, V> mapWrapper(Map<K, V> build) {
        if (UnmodifiableMap.class.isAssignableFrom(mapType)) {
            return build;
        } else if (UnmodifiableSortedMap.class.isAssignableFrom(mapType)) {
            return UnmodifiableSortedMap.asSortedMap(build);
        } else if (LinkedHashMap.class.isAssignableFrom(mapType)) {
            return new LinkedHashMap<>(build);
        } else if (SortedMap.class.isAssignableFrom(mapType)) {
            return new TreeMap<>(build);
        } else {
            return new HashMap<>(build);
        }
    }
}