Skip to content

Commit

Permalink
Add ObjectMapper Module for Id de-/serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
marecabo committed Oct 13, 2023
1 parent 68e9527 commit 7d802bb
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 0 deletions.
28 changes: 28 additions & 0 deletions matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,6 +83,15 @@ public Id<Person> 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)
Expand Down Expand Up @@ -125,6 +135,15 @@ public Id<Link> 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)
Expand Down Expand Up @@ -168,6 +187,15 @@ public Id<Node> 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);
}

}

}

}
Original file line number Diff line number Diff line change
@@ -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<Class<?>, Triple<JsonSerializer<?>, JsonDeserializer<?>, KeyDeserializer>> DE_SERIALIZER_MAP;
static {
Map<Class<?>, Triple<JsonSerializer<?>, 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;
}

}

}
Original file line number Diff line number Diff line change
@@ -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<T> as keys
Map<Id<Link>, 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<Id<Link>, 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<T> as values
Map<String, Id<Link>> 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<String, Id<Link>> map1;
try {
map1 = objectMapper.readValue(s, mapType);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
Assert.assertEquals(map0, map1);
}

}

0 comments on commit 7d802bb

Please sign in to comment.