Skip to content

Commit

Permalink
Issue #283: Deserializing Map with enum keys results in runtime strin…
Browse files Browse the repository at this point in the history
…g keys

Signed-off-by: rmartinc <[email protected]>
  • Loading branch information
rmartinc committed Sep 3, 2021
1 parent 9d9fffa commit d6b6999
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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 @@ -67,7 +67,7 @@ Class<?> getComponentClass() {
}

@Override
public void appendResult(Object result) {
public void appendResult(Object result, Unmarshaller context) {
appendCaptor(convertNullToOptionalEmpty(componentClass, result));
}

Expand All @@ -80,7 +80,7 @@ private <X> void appendCaptor(X value) {
protected void deserializeNext(JsonParser parser, Unmarshaller context) {
final JsonbDeserializer<?> deserializer = newUnmarshallerItemBuilder(context.getJsonbContext()).withType(componentClass)
.withCustomization(componentClassModel == null ? null : componentClassModel.getClassCustomization()).build();
appendResult(deserializer.deserialize(parser, context, componentClass));
appendResult(deserializer.deserialize(parser, context, componentClass), context);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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 @@ -104,7 +104,7 @@ protected void deserializeInternal(JsonbParser parser, Unmarshaller context) {
case KEY_NAME:
break;
case VALUE_NULL:
appendResult(null);
appendResult(null, context);
break;
case END_OBJECT:
case END_ARRAY:
Expand Down Expand Up @@ -190,8 +190,9 @@ protected Object convertNullToOptionalEmpty(Type propertyType, Object value) {
* or other embedded objects use methods provided.
*
* @param result An instance result of an item.
* @param context Current unmarshalling context.
*/
public abstract void appendResult(Object result);
public abstract void appendResult(Object result, Unmarshaller context);

/**
* Returns parser context.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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 @@ -47,7 +47,7 @@ protected void deserializeNext(JsonParser parser, Unmarshaller context) {
}

@Override
public void appendResult(Object result) {
public void appendResult(Object result, Unmarshaller context) {
throw new UnsupportedOperationException("Inner json structures are deserialized by JsonParser.");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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 @@ -102,7 +102,7 @@ public T getInstance(Unmarshaller unmarshaller) {
}

@Override
public void appendResult(Object result) {
public void appendResult(Object result, Unmarshaller context) {
appendCaptor(convertNullToOptionalEmpty(collectionValueType, result));
}

Expand All @@ -114,7 +114,7 @@ private <T> void appendCaptor(T object) {
@Override
protected void deserializeNext(JsonParser parser, Unmarshaller context) {
final JsonbDeserializer<?> deserializer = newCollectionOrMapItem(collectionValueType, context.getJsonbContext());
appendResult(deserializer.deserialize(parser, context, collectionValueType));
appendResult(deserializer.deserialize(parser, context, collectionValueType), context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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 @@ -12,6 +12,7 @@

package org.eclipse.yasson.internal.serializer;

import java.io.StringReader;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
Expand All @@ -33,15 +34,22 @@

/**
* Item implementation for {@link java.util.Map} fields.
* According to JSON specification object can have only string keys, given that maps could only be parsed
* from JSON objects, implementation is bound to String type.
* According to JSON specification object can have only string keys.
* Nevertheless the implementation lets the key be a basic object that was
* serialized into a string representation. Therefore the key is also parsed to
* convert it into its parametrized type.
*
* @param <T> map type
*/
public class MapDeserializer<T extends Map<?, ?>> extends AbstractContainerDeserializer<T> implements EmbeddedItem {

/**
* Type of value in the map. (Keys must always be Strings, because of JSON spec)
* Type of the key in the map.
*/
private final Type mapKeyRuntimeType;

/**
* Type of value in the map.
*/
private final Type mapValueRuntimeType;

Expand All @@ -54,6 +62,9 @@ public class MapDeserializer<T extends Map<?, ?>> extends AbstractContainerDeser
*/
protected MapDeserializer(DeserializerBuilder builder) {
super(builder);
mapKeyRuntimeType = getRuntimeType() instanceof ParameterizedType
? ReflectionUtils.resolveType(this, ((ParameterizedType) getRuntimeType()).getActualTypeArguments()[0])
: Object.class;
mapValueRuntimeType = getRuntimeType() instanceof ParameterizedType
? ReflectionUtils.resolveType(this, ((ParameterizedType) getRuntimeType()).getActualTypeArguments()[1])
: Object.class;
Expand Down Expand Up @@ -93,19 +104,23 @@ public T getInstance(Unmarshaller unmarshaller) {
}

@Override
public void appendResult(Object result) {
appendCaptor(getParserContext().getLastKeyName(), convertNullToOptionalEmpty(mapValueRuntimeType, result));
public void appendResult(Object result, Unmarshaller context) {
// try to deserialize the string key into its type, JaxbException if not possible
final Object key = context.deserialize(mapKeyRuntimeType, new JsonbRiParser(
context.getJsonbContext().getJsonProvider().createParser(
new StringReader("\"" + getParserContext().getLastKeyName() + "\""))));
appendCaptor(key, convertNullToOptionalEmpty(mapValueRuntimeType, result));
}

@SuppressWarnings("unchecked")
private <V> void appendCaptor(String key, V value) {
((Map<String, V>) getInstance(null)).put(key, value);
private <K, V> void appendCaptor(K key, V value) {
((Map<K, V>) getInstance(null)).put(key, value);
}

@Override
protected void deserializeNext(JsonParser parser, Unmarshaller context) {
final JsonbDeserializer<?> deserializer = newCollectionOrMapItem(mapValueRuntimeType, context.getJsonbContext());
appendResult(deserializer.deserialize(parser, context, mapValueRuntimeType));
appendResult(deserializer.deserialize(parser, context, mapValueRuntimeType), context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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 @@ -110,7 +110,6 @@ protected void beforeSerialize(Map<K, V> obj) {
if (serializer == null) {
// All keys can be serialized as String
boolean allStrings = true;
boolean first = true;
Class<? extends Object> cls = null;
// Cycle shall exit on first negative check
for (Iterator<? extends Object> i = obj.keySet().iterator(); allStrings && i.hasNext(); ) {
Expand All @@ -125,11 +124,6 @@ protected void beforeSerialize(Map<K, V> obj) {
// 1st pass: check whether key type is supported for Map to JSON Object serialization
} else if (key instanceof String || key instanceof Number || key instanceof Enum) {
cls = key.getClass();
first = false;
// 1st pass: check whether key is null, which is also supported for Map to JSON Object serialization
// Map shall contain only single mapping for null value and nothing else
} else if (key == null && first) {
first = false;
} else {
allStrings = false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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 @@ -137,9 +137,10 @@ private T createInstance(Class<T> rawType, JsonbCreator creator) {
* Set populated instance of current object to its unfinished wrapper values map.
*
* @param result An instance result of an item.
* @param context Current unmarshalling context.
*/
@Override
public void appendResult(Object result) {
public void appendResult(Object result, Unmarshaller context) {
final PropertyModel model = getModel();
//missing property for null values
if (model == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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 @@ -46,7 +46,7 @@ protected UserDeserializerDeserializer(DeserializerBuilder builder, Deserializer
}

@Override
public void appendResult(Object result) {
public void appendResult(Object result, Unmarshaller context) {
//ignore internal deserialize() call in custom deserializer
}

Expand Down
Loading

0 comments on commit d6b6999

Please sign in to comment.