Skip to content

Commit

Permalink
Issue #500: Serialization of a Map fails if the key uses a custom Ser…
Browse files Browse the repository at this point in the history
…ializer (#501)

Signed-off-by: rmartinc <rmartinc@redhat.com>
  • Loading branch information
rmartinc authored Nov 16, 2021
1 parent 7a5b15a commit dd42d0a
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@

package org.eclipse.yasson.internal.serializer;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;

import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;

import org.eclipse.yasson.internal.ReflectionUtils;

/**
* Serialize {@link Map}.
*
Expand Down Expand Up @@ -90,13 +95,19 @@ default void writeEnd(JsonGenerator generator) {
*/
private Delegate<K, V> serializer;

/**
* Flag to know if the process is for the key (0) or the value (1).
*/
private int actualTypeArgument;

/**
* Creates an instance of {@link Map} serialization.
*
* @param builder current instance of {@link SerializerBuilder}
*/
protected MapSerializer(SerializerBuilder builder) {
super(builder);
actualTypeArgument = 0;
nullable = builder.getJsonbContext().getConfigProperties().getConfigNullable();
forceMapArraySerializerForNullKeys = builder.getJsonbContext().getConfigProperties().isForceMapArraySerializerForNullKeys();
serializer = null;
Expand Down Expand Up @@ -203,4 +214,35 @@ protected boolean isNullable() {
return nullable;
}

/**
* Flag to serialize the key in the map.
*/
protected void serializeKey() {
this.actualTypeArgument = 0;
}

/**
* Flag to serialize the value in the map.
*/
protected void serializeValue() {
this.actualTypeArgument = 1;
}

/**
* In a map the type can refer to the key or the value type depending which
* one is currently being processed. The field <em>actualTypeArgument</em>
* controls which one is being serialized at the moment.
*
* @param valueType The value type which should be of type Map&lt;K,V&gt;
* @return The type for the key or the value
*/
@Override
protected Type getValueType(Type valueType) {
if (valueType instanceof ParameterizedType && ((ParameterizedType) valueType).getActualTypeArguments().length > actualTypeArgument) {
Optional<Type> runtimeTypeOptional = ReflectionUtils
.resolveOptionalType(this, ((ParameterizedType) valueType).getActualTypeArguments()[actualTypeArgument]);
return runtimeTypeOptional.orElse(Object.class);
}
return Object.class;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -106,8 +106,10 @@ public void serializeContainer(Map<K, V> obj, JsonGenerator generator, Serializa
obj.forEach((key, value) -> {
generator.writeStartObject();
generator.writeKey(keyEntryName);
serializer.serializeKey();
serializer.serializeItem(key, generator, ctx);
generator.writeKey(valueEntryName);
serializer.serializeValue();
serializer.serializeItem(value, generator, ctx);
generator.writeEnd();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -96,6 +96,7 @@ public void serializeContainer(Map<K, V> obj, JsonGenerator generator, Serializa
continue;
}
generator.writeKey(keyString);
serializer.serializeValue();
serializer.serializeItem(value, generator, ctx);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -21,16 +21,25 @@
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.serializer.DeserializationContext;
import jakarta.json.bind.serializer.JsonbDeserializer;
import jakarta.json.bind.serializer.JsonbSerializer;
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;
import jakarta.json.stream.JsonParser;

import org.eclipse.yasson.serializers.model.Pokemon;
import org.eclipse.yasson.serializers.model.Trainer;
Expand Down Expand Up @@ -827,4 +836,103 @@ public Type getOwnerType() {
}
}

public static class LocaleSerializer implements JsonbSerializer<Locale> {

@Override
public void serialize(Locale obj, JsonGenerator generator, SerializationContext ctx) {
generator.write(obj.toLanguageTag());
}
}

public static class LocaleDeserializer implements JsonbDeserializer<Locale> {

@Override
public Locale deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
return Locale.forLanguageTag(parser.getString());
}
}

public static class MapObject<K, V> {

private Map<K, V> values;

public MapObject() {
this.values = new HashMap<>();
}

public Map<K, V> getValues() {
return values;
}

public void setValues(Map<K, V> values) {
if (values == null) {
throw new IllegalArgumentException("values cannot be null");
}
this.values = values;
}

@Override
public boolean equals(Object o) {
if (o instanceof MapObject) {
MapObject<?,?> to = (MapObject<?,?>) o;
return values.equals(to.values);
}
return false;
}

@Override
public int hashCode() {
return Objects.hashCode(this.values);
}

@Override
public String toString() {
return values.toString();
}
}

public static class MapObjectLocaleString extends MapObject<Locale, String> {};

private void verifyMapObjectLocaleStringSerialization(JsonObject jsonObject, MapObjectLocaleString mapObject) {
// Expected serialization is: {"values":[{"key":"lang-tag","value":"string"},...]}
assertEquals(1, jsonObject.size());
assertNotNull(jsonObject.get("values"));
assertEquals(JsonValue.ValueType.ARRAY, jsonObject.get("values").getValueType());
JsonArray jsonArray = jsonObject.getJsonArray("values");
assertEquals(mapObject.getValues().size(), jsonArray.size());
MapObjectLocaleString resObject = new MapObjectLocaleString();
for (JsonValue jsonValue : jsonArray) {
assertEquals(JsonValue.ValueType.OBJECT, jsonValue.getValueType());
JsonObject entry = jsonValue.asJsonObject();
assertEquals(2, entry.size());
assertNotNull(entry.get("key"));
assertEquals(JsonValue.ValueType.STRING, entry.get("key").getValueType());
assertNotNull(entry.get("value"));
assertEquals(JsonValue.ValueType.STRING, entry.get("value").getValueType());
resObject.getValues().put(Locale.forLanguageTag(entry.getString("key")), entry.getString("value"));
}
assertEquals(mapObject, resObject);
}

/**
* Test a Locale/String map with custom Locale serializer and deserializer.
*/
@Test
public void testMapLocaleString() {
Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
.withSerializers(new LocaleSerializer())
.withDeserializers(new LocaleDeserializer()));

MapObjectLocaleString mapObject = new MapObjectLocaleString();
mapObject.getValues().put(Locale.US, "us");
mapObject.getValues().put(Locale.ENGLISH, "en");
mapObject.getValues().put(Locale.JAPAN, "jp");

String json = jsonb.toJson(mapObject);
JsonObject jsonObject = Json.createReader(new StringReader(json)).read().asJsonObject();
verifyMapObjectLocaleStringSerialization(jsonObject, mapObject);

MapObjectLocaleString resObject = jsonb.fromJson(json, MapObjectLocaleString.class);
assertEquals(mapObject, resObject);
}
}

0 comments on commit dd42d0a

Please sign in to comment.