diff --git a/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java b/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java index c7787f12d41..7e738d2bd54 100644 --- a/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java +++ b/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java @@ -33,6 +33,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -82,6 +83,15 @@ public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws } + static class PersonIdKeyDeserializer extends KeyDeserializer { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return Id.createPersonId(key); + } + + } + } @Retention(RetentionPolicy.RUNTIME) @@ -125,6 +135,15 @@ public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws I } + static class LinkIdKeyDeserializer extends KeyDeserializer { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return Id.createLinkId(key); + } + + } + } @Retention(RetentionPolicy.RUNTIME) @@ -168,6 +187,15 @@ public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws I } + static class NodeIdKeyDeserializer extends KeyDeserializer { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return Id.createNodeId(key); + } + + } + } } diff --git a/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java b/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java new file mode 100644 index 00000000000..d2778358d6e --- /dev/null +++ b/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java @@ -0,0 +1,127 @@ +package org.matsim.api.core.v01; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.tuple.Triple; +import org.matsim.api.core.v01.IdAnnotations.JsonLinkId; +import org.matsim.api.core.v01.IdAnnotations.JsonNodeId; +import org.matsim.api.core.v01.IdAnnotations.JsonPersonId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Node; +import org.matsim.api.core.v01.population.Person; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.deser.Deserializers; +import com.fasterxml.jackson.databind.deser.KeyDeserializers; +import com.fasterxml.jackson.databind.ser.Serializers; + +/** + * Use as follows with your {@link ObjectMapper} instance: + * + * {@code objectMapper.registerModule(IdDeSerializationModule.getInstance());} + */ +public class IdDeSerializationModule extends Module { + + private static final String NAME = IdDeSerializationModule.class.getSimpleName(); + private static final Version VERSION = new Version(0, 1, 0, null, "org.matsim", "api.core.v01"); + private static final Map, Triple, JsonDeserializer, KeyDeserializer>> DE_SERIALIZER_MAP; + static { + Map, Triple, JsonDeserializer, KeyDeserializer>> m = new HashMap<>(); + m.put(Person.class, Triple.of( + new JsonPersonId.PersonIdSerializer(), + new JsonPersonId.PersonIdDeserializer(), + new JsonPersonId.PersonIdKeyDeserializer())); + m.put(Node.class, Triple.of( + new JsonNodeId.NodeIdSerializer(), + new JsonNodeId.NodeIdDeserializer(), + new JsonNodeId.NodeIdKeyDeserializer())); + m.put(Link.class, Triple.of( + new JsonLinkId.LinkIdSerializer(), + new JsonLinkId.LinkIdDeserializer(), + new JsonLinkId.LinkIdKeyDeserializer())); + // Add your own classes below here + DE_SERIALIZER_MAP = Collections.unmodifiableMap(m); + } + + private static Module instance = null; + + private IdDeSerializationModule() { + // nothing to do here + } + + public static Module getInstance() { + if (instance == null) { + instance = new IdDeSerializationModule(); + } + return instance; + } + + @Override + public String getModuleName() { + return NAME; + } + + @Override + public Version version() { + return VERSION; + } + + @Override + public void setupModule(SetupContext context) { + context.addSerializers(new IdSerializers()); + context.addDeserializers(new IdDeserializers()); + context.addKeyDeserializers(new IdKeyDeserializers()); + } + + private static final class IdSerializers extends Serializers.Base { + + @Override + public JsonSerializer findSerializer(SerializationConfig config, JavaType type, + BeanDescription beanDesc) { + if (type.getRawClass().equals(Id.class) && type.containedTypeCount() == 1) { + return DE_SERIALIZER_MAP.get(type.containedType(0).getRawClass()).getLeft(); + } + return null; + } + + } + + private static final class IdDeserializers extends Deserializers.Base { + + @Override + public JsonDeserializer findBeanDeserializer(JavaType type, DeserializationConfig config, + BeanDescription beanDesc) throws JsonMappingException { + if (type.getRawClass().equals(Id.class) && type.containedTypeCount() == 1) { + return DE_SERIALIZER_MAP.get(type.containedType(0).getRawClass()).getMiddle(); + } + return null; + } + + } + + private static final class IdKeyDeserializers implements KeyDeserializers { + + @Override + public KeyDeserializer findKeyDeserializer(JavaType type, DeserializationConfig config, + BeanDescription beanDesc) throws JsonMappingException { + if (type.getRawClass().equals(Id.class) && type.containedTypeCount() == 1) { + return DE_SERIALIZER_MAP.get(type.containedType(0).getRawClass()).getRight(); + } + return null; + } + + } + +} \ No newline at end of file diff --git a/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java b/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java new file mode 100644 index 00000000000..799ca6c8fbf --- /dev/null +++ b/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java @@ -0,0 +1,94 @@ +package org.matsim.api.core.v01; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.matsim.api.core.v01.network.Link; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.type.MapType; +import com.fasterxml.jackson.databind.type.TypeFactory; + +public class IdDeSerializationModuleTest { + + private static final TypeFactory TYPE_FACTORY = TypeFactory.defaultInstance(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Before + public void init() { + this.objectMapper.registerModule(IdDeSerializationModule.getInstance()); + } + + @Test + public void testMapKey() { + + // create map with Id as keys + Map, String> map0 = new LinkedHashMap<>(); + map0.put(Id.createLinkId("0"), "a"); + map0.put(Id.createLinkId("1"), "b"); + + // build writer + JavaType linkIdType = TYPE_FACTORY.constructParametricType(Id.class, Link.class); + MapType mapType = TYPE_FACTORY.constructMapType(Map.class, linkIdType, TYPE_FACTORY.constructType(String.class)); + ObjectWriter objectWriter = objectMapper.writerFor(mapType); + + // serialize + String s; + try { + s = objectWriter.writeValueAsString(map0); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + System.out.println(s); + Assert.assertEquals("{\"0\":\"a\",\"1\":\"b\"}", s); + + // deserialize + Map, String> map1; + try { + map1 = objectMapper.readValue(s, mapType); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + Assert.assertEquals(map0, map1); + } + + @Test + public void testMapValue() { + + // create map with Id as values + Map> map0 = new LinkedHashMap<>(); + map0.put("a", Id.createLinkId("0")); + map0.put("b", Id.createLinkId("1")); + + // build writer + JavaType linkIdType = TYPE_FACTORY.constructParametricType(Id.class, Link.class); + MapType mapType = TypeFactory.defaultInstance().constructMapType(Map.class, TYPE_FACTORY.constructType(String.class), linkIdType); + ObjectWriter objectWriter = objectMapper.writerFor(mapType); + + // serialize + String s; + try { + s = objectWriter.writeValueAsString(map0); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + System.out.println(s); + Assert.assertEquals("{\"a\":\"0\",\"b\":\"1\"}", s); + + // deserialize + Map> map1; + try { + map1 = objectMapper.readValue(s, mapType); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + Assert.assertEquals(map0, map1); + } + +}