Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for arbitrary map key types serialisation and deserialisation #8

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
junitVersion=4.12
vavrVersion=1.0.0-SNAPSHOT
vavrVersion=0.10.3
gsonVersion=2.8.0
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

abstract class JsonArrayConverter<T> implements JsonSerializer<T>, JsonDeserializer<T> {
abstract class ArrayTypeConverter<T> implements JsonSerializer<T>, JsonDeserializer<T> {

private static final Type[] EMPTY_TYPES = new Type[0];

Expand Down
44 changes: 0 additions & 44 deletions src/main/java/io/vavr/gson/JsonObjectConverter.java

This file was deleted.

81 changes: 69 additions & 12 deletions src/main/java/io/vavr/gson/MapConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,94 @@
*/
package io.vavr.gson;

import com.google.gson.*;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.collection.Map;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

class MapConverter<K, V, T extends Map<K, V>> extends JsonObjectConverter<T> {
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.collection.Map;
import io.vavr.collection.Traversable;
import io.vavr.collection.Vector;

private final Function<Iterable<Tuple2<String, ?>>, Map<?, ?>> factory;
class MapConverter<K, V, T extends Map<K, V>> extends MapTypeConverter<T> {

MapConverter(Function<Iterable<Tuple2<String, ?>>, Map<?, ?>> factory) {
private final Function<Iterable<Tuple2<K, V>>, Map<K, V>> factory;

MapConverter(Function<Iterable<Tuple2<K, V>>, Map<K, V>> factory) {
this.factory = factory;
}

@Override
@SuppressWarnings("unchecked")
T fromJsonObject(JsonObject obj, Type type, Type[] subTypes, JsonDeserializationContext ctx) throws JsonParseException {
Function<java.util.Map.Entry<String, JsonElement>, Tuple2<String, ?>> mapper;
Function<java.util.Map.Entry<String, JsonElement>, Tuple2<K, V>> mapper;
if (subTypes.length == 2) {
mapper = e -> Tuple.of(ctx.deserialize(new JsonPrimitive(e.getKey()), subTypes[0]), ctx.deserialize(e.getValue(), subTypes[1]));
mapper = e -> convert(ctx, e, subTypes[0], subTypes[1]);
} else {
mapper = e -> Tuple.of(e.getKey(), e.getValue());
mapper = e -> Tuple.of((K) e.getKey(), (V) e.getValue());
}
return (T) factory.apply(obj.entrySet().stream().map(mapper).collect(Collectors.toList()));
final Set<java.util.Map.Entry<String, JsonElement>> entries = obj.entrySet();
final Iterable<Tuple2<K, V>> collect = entries.stream().map(mapper).collect(Collectors.toList());
return (T) factory.apply(collect);
}

@Override
@SuppressWarnings("unchecked")
T fromJsonArray(JsonArray arr, Type type, Type[] subTypes, JsonDeserializationContext ctx) throws JsonParseException {
final Awkward tuple2KV = new Awkward(Tuple2.class, subTypes);
final Type traversableType = new TypeToken<Traversable<?>>() {
}.getType();
final Awkward traversableTuple2KV = new Awkward(traversableType, tuple2KV);
final TraversableConverter<Traversable<Tuple2<K, V>>> traversableConverter = new TraversableConverter<>(Vector::ofAll);
final Traversable<Tuple2<K, V>> tuple2s = traversableConverter.fromJsonArray(arr, traversableTuple2KV, traversableTuple2KV.getActualTypeArguments(), ctx);
return (T) factory.apply(tuple2s);
}

private Tuple2<K, V> convert(JsonDeserializationContext ctx, java.util.Map.Entry<String, JsonElement> e, Type keyType, Type valueType) {
final JsonPrimitive json = new JsonPrimitive(e.getKey());
final K key = ctx.deserialize(json, keyType);
final V value = ctx.deserialize(e.getValue(), valueType);
return Tuple.of(key, value);
}

@Override
Map<K, V> toMap(T src) {
return src;
}

private static final class Awkward implements ParameterizedType {

private final Type rawType;
private final Type[] subTypes;

private Awkward(Type rawType, Type... subTypes) {
this.rawType = rawType;
this.subTypes = subTypes;
}

@Override
public Type[] getActualTypeArguments() {
return subTypes;
}

@Override
public Type getRawType() {
return rawType;
}

@Override
public Type getOwnerType() {
return null;
}
}
}
81 changes: 81 additions & 0 deletions src/main/java/io/vavr/gson/MapTypeConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.gson;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import io.vavr.collection.Map;

import static jdk.nashorn.internal.runtime.JSType.isPrimitive;

abstract class MapTypeConverter<T> implements JsonSerializer<T>, JsonDeserializer<T> {

private static final Type[] EMPTY_TYPES = new Type[0];

abstract T fromJsonObject(JsonObject arr, Type type, Type[] subTypes, JsonDeserializationContext ctx) throws JsonParseException;

abstract T fromJsonArray(JsonArray arr, Type type, Type[] subTypes, JsonDeserializationContext ctx) throws JsonParseException;

abstract Map<?, ?> toMap(T src);

@Override
public T deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException {
if (json.isJsonObject()) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] types = parameterizedType.getActualTypeArguments();
return fromJsonObject(json.getAsJsonObject(), type, types, ctx);
} else {
return fromJsonObject(json.getAsJsonObject(), type, EMPTY_TYPES, ctx);
}
} else if (json.isJsonArray()) {
// list of tuples
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] types = parameterizedType.getActualTypeArguments();
return fromJsonArray(json.getAsJsonArray(), parameterizedType, types, ctx);
} else {
throw new JsonParseException("unsupported non-parameterised types for complex map keys");
}
} else {
throw new JsonParseException("object or array expected");
}
}

@Override
public JsonElement serialize(T src, Type type, JsonSerializationContext ctx) {
final Map<?, ?> tuple2s = toMap(src);
if (tuple2s.isEmpty()) {
return new JsonObject();
} else {
// test the first element
final Object o = tuple2s.head()._1();
// primitives are fine
if (isPrimitive(o)) {
return tuple2s.foldLeft(new JsonObject(), (a, e) -> {
a.add(ctx.serialize(e._1).getAsString(), ctx.serialize(e._2));
return a;
});
} else {
//complex object key serialisation
return tuple2s.foldLeft(new JsonArray(), (a, e) -> {
a.add(ctx.serialize(e));
return a;
});
}
}
}
}
27 changes: 19 additions & 8 deletions src/main/java/io/vavr/gson/MultimapConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@
*/
package io.vavr.gson;

import com.google.gson.*;
import java.lang.reflect.Type;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import io.vavr.Function1;
import io.vavr.Tuple;
import io.vavr.Tuple2;
Expand All @@ -15,13 +26,7 @@
import io.vavr.collection.Map;
import io.vavr.collection.Multimap;

import java.lang.reflect.Type;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

class MultimapConverter<K, V, T extends Multimap<K, V>> extends JsonObjectConverter<T> {
class MultimapConverter<K, V, T extends Multimap<K, V>> extends MapTypeConverter<T> {

private final Function<Iterable<Tuple2<String, ?>>, Multimap<?, ?>> factory;

Expand All @@ -41,6 +46,12 @@ T fromJsonObject(JsonObject obj, Type type, Type[] subTypes, JsonDeserialization
return (T) factory.apply(obj.entrySet().stream().flatMap(mapper).collect(Collectors.toList()));
}

@Override
@SuppressWarnings("unchecked")
T fromJsonArray(JsonArray arr, Type type, Type[] subTypes, JsonDeserializationContext ctx) throws JsonParseException {
throw new UnsupportedOperationException("Array types not supported for Multimaps");
}

@Override
@SuppressWarnings("unchecked")
Map<K, List<V>> toMap(T src) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/vavr/gson/OptionConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import java.lang.reflect.Type;

class OptionConverter extends JsonArrayConverter<Option<?>> {
class OptionConverter extends ArrayTypeConverter<Option<?>> {

@Override
Option<?> fromJsonArray(JsonArray arr, Type type, Type[] subTypes, JsonDeserializationContext ctx) throws JsonParseException {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/vavr/gson/TraversableConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.lang.reflect.Type;
import java.util.function.Function;

class TraversableConverter<T extends Traversable<?>> extends JsonArrayConverter<T> {
class TraversableConverter<T extends Traversable<?>> extends ArrayTypeConverter<T> {

private final Function<Iterable<?>, Traversable<?>> factory;

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/vavr/gson/TupleConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import java.lang.reflect.Type;

abstract class TupleConverter<T> extends JsonArrayConverter<T> {
abstract class TupleConverter<T> extends ArrayTypeConverter<T> {

static class N0 extends TupleConverter<Tuple0> {

Expand Down
10 changes: 8 additions & 2 deletions src/main/java/io/vavr/gson/VavrGson.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;

import com.google.gson.GsonBuilder;
import io.vavr.*;
Expand Down Expand Up @@ -378,10 +379,15 @@ public static Map<Type, Object> multiMapTypeAdapters() {
public static Map<Type, Object> mapTypeAdapters() {
return API.<Type, Object>Map()
.put(Map.class, new MapConverter<>(HashMap::ofEntries))
.put(SortedMap.class, new MapConverter<>(TreeMap::ofEntries))
.put(SortedMap.class, new MapConverter<>(VavrGson::getTreeMapMapper))
.put(HashMap.class, new MapConverter<>(HashMap::ofEntries))
.put(LinkedHashMap.class, new MapConverter<>(LinkedHashMap::ofEntries))
.put(TreeMap.class, new MapConverter<>(TreeMap::ofEntries));
.put(TreeMap.class, new MapConverter<>(VavrGson::getTreeMapMapper));
}


private static <K,V> TreeMap<K, V> getTreeMapMapper(Iterable<Tuple2<K, V>> i) {
return TreeMap.ofEntries((a, b) -> ((Comparable) a).compareTo(b), i);
}

public static Map<Type, Object> collectionTypeAdapters() {
Expand Down
1 change: 1 addition & 0 deletions src/test/java/io/vavr/gson/AbstractTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class AbstractTest {
public static void before() {
GsonBuilder builder = new GsonBuilder();
VavrGson.registerAll(builder);
builder = builder.enableComplexMapKeySerialization();
gson = builder.create();
}

Expand Down
Loading