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

Add ObjectMapper Module for Id de-/serialization #2849

Merged
merged 2 commits into from
Oct 13, 2023
Merged
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
160 changes: 63 additions & 97 deletions matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,149 +23,115 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

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 java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

public interface IdAnnotations {

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = JsonPersonId.PersonIdSerializer.class)
@JsonDeserialize(using = JsonPersonId.PersonIdDeserializer.class)
public @interface JsonPersonId {

static class PersonIdSerializer extends StdSerializer<Id<Person>> {
@JsonSerialize(using = JsonIdSerializer.class)
@JsonDeserialize(using = JsonIdContextualDeserializer.class)
public @interface JsonId {

protected PersonIdSerializer() {
this(null);
}

protected PersonIdSerializer(Class<Id<Person>> vc) {
super(vc);
}
}

@Override
public void serialize(Id<Person> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.toString());
}
class JsonIdSerializer<T> extends StdSerializer<T> {

protected JsonIdSerializer() {
this(null);
}

static class PersonIdDeserializer extends StdDeserializer<Id<Person>> {

protected PersonIdDeserializer() {
this(null);
}

protected PersonIdDeserializer(Class<?> vc) {
super(vc);
}

@Override
public Id<Person> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return Id.createPersonId(node.asText());
}
protected JsonIdSerializer(Class<T> vc) {
super(vc);
}

@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.toString());
}

}

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = JsonLinkId.LinkIdSerializer.class)
@JsonDeserialize(using = JsonLinkId.LinkIdDeserializer.class)
public @interface JsonLinkId {

static class LinkIdSerializer extends StdSerializer<Id<Link>> {

protected LinkIdSerializer() {
this(null);
}

protected LinkIdSerializer(Class<Id<Link>> vc) {
super(vc);
}
class JsonIdContextualDeserializer<T> extends StdDeserializer<Id<T>> implements ContextualDeserializer {

@Override
public void serialize(Id<Link> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.toString());
}
private Class<T> idClass;

protected JsonIdContextualDeserializer() {
this(null);
}

static class LinkIdDeserializer extends StdDeserializer<Id<Link>> {
protected JsonIdContextualDeserializer(Class<T> idClass) {
super(Object.class);
this.idClass = idClass;
}

protected LinkIdDeserializer() {
this(null);
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {

protected LinkIdDeserializer(Class<?> vc) {
super(vc);
final Class<? extends Object> idClass;
{
final JavaType type;
if (property != null)
type = property.getType();
else {
type = ctxt.getContextualType();
}
idClass = type.containedType(0).getRawClass();
}

@Override
public Id<Link> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return Id.createLinkId(node.asText());
}
return JsonIdDeserializer.getInstance(idClass);
}

@Override
public Id<T> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = jp.getCodec().readTree(jp);
return Id.create(node.asText(), idClass);
}

}

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = JsonNodeId.NodeIdSerializer.class)
@JsonDeserialize(using = JsonNodeId.NodeIdDeserializer.class)
public @interface JsonNodeId {

static class NodeIdSerializer extends StdSerializer<Id<Node>> {

protected NodeIdSerializer() {
this(null);
}
class JsonIdDeserializer<T> extends StdDeserializer<Id<T>> {

protected NodeIdSerializer(Class<Id<Node>> vc) {
super(vc);
}

@Override
public void serialize(Id<Node> value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.toString());
}
private static final Map<Class<?>, JsonIdDeserializer<?>> CACHE = new HashMap<>();

public static JsonIdDeserializer<?> getInstance(Class<?> clazz) {
return CACHE.computeIfAbsent(clazz, k -> new JsonIdDeserializer<>(k));
}

static class NodeIdDeserializer extends StdDeserializer<Id<Node>> {

protected NodeIdDeserializer() {
this(null);
}
private final Class<T> idClass;

protected NodeIdDeserializer(Class<?> vc) {
super(vc);
}
private JsonIdDeserializer() {
this(null);
}

@Override
public Id<Node> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return Id.createNodeId(node.asText());
}
private JsonIdDeserializer(Class<T> idClass) {
super(Object.class);
this.idClass = idClass;
}

@Override
public Id<T> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return Id.create(node.asText(), idClass);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.matsim.api.core.v01;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
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;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
* 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 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 StdSerializer<?> SERIALIZER = new IdAnnotations.JsonIdSerializer<>(Id.class);
private static final Map<Class<?>, KeyDeserializer> KEY_DESERIALIZER_CACHE = new HashMap<>();

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 SERIALIZER;
}
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 IdAnnotations.JsonIdDeserializer.getInstance(type.containedType(0).getRawClass());
}
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 KEY_DESERIALIZER_CACHE.computeIfAbsent(type.containedType(0).getRawClass(),
k -> new KeyDeserializer() {

@Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return Id.create(key, k);
}

});
}
return null;
}

}

}
Loading
Loading