MapKeyDeserializationTest.java
package com.fasterxml.jackson.databind.deser.jdk;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import static org.junit.jupiter.api.Assertions.*;
import static com.fasterxml.jackson.databind.testutil.DatabindTestUtil.*;
public class MapKeyDeserializationTest
{
static class FullName {
String _firstname, _lastname;
private FullName(String firstname, String lastname) {
_firstname = firstname;
_lastname = lastname;
}
@JsonCreator
public static FullName valueOf(String value) {
String[] mySplit = value.split("\\.");
return new FullName(mySplit[0], mySplit[1]);
}
public static FullName valueOf(String firstname, String lastname) {
return new FullName(firstname, lastname);
}
@JsonValue
@Override
public String toString() {
return _firstname + "." + _lastname;
}
}
// [databind#2725]
enum TestEnum2725 {
FOO(1);
private final int i;
TestEnum2725(final int i) {
this.i = i;
}
@JsonValue
public int getI() {
return i;
}
@JsonCreator
public static TestEnum2725 getByIntegerId(final Integer id) {
return id == FOO.i ? FOO : null;
}
@JsonCreator
public static TestEnum2725 getByStringId(final String id) {
return Integer.parseInt(id) == FOO.i ? FOO : null;
}
}
// for [databind#2158]
private static final class DummyDto2158 {
@JsonValue
private final String value;
private DummyDto2158(String value) {
this.value = value;
}
@JsonCreator
static DummyDto2158 fromValue(String value) {
if (value.isEmpty()) {
throw new IllegalArgumentException("Value must be nonempty");
}
return new DummyDto2158(value.toLowerCase(Locale.ROOT));
}
@Override
public boolean equals(Object o) {
return o instanceof DummyDto2158 && ((DummyDto2158) o).value.equals(value);
}
@Override
public int hashCode() { return Objects.hash(value); }
@Override
public String toString() { return String.format("DummyDto{value=%s}", value); }
}
private static final TypeReference<Map<DummyDto2158, Integer>> MAP_TYPE_2158 =
new TypeReference<Map<DummyDto2158, Integer>>() {};
/*
/**********************************************************************
/* Test methods, wrapper keys
/**********************************************************************
*/
final private ObjectMapper MAPPER = newJsonMapper();
@Test
public void testBooleanMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Boolean, String>> type = new TypeReference<MapWrapper<Boolean, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'true':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Boolean.TRUE, result.map.entrySet().iterator().next().getKey());
result = MAPPER.readValue(a2q("{'map':{'false':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Boolean.FALSE, result.map.entrySet().iterator().next().getKey());
}
@Test
public void testByteMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Byte, String>> type = new TypeReference<MapWrapper<Byte, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'13':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Byte.valueOf((byte) 13), result.map.entrySet().iterator().next().getKey());
}
@Test
public void testShortMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Short, String>> type = new TypeReference<MapWrapper<Short, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'13':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Short.valueOf((short) 13), result.map.entrySet().iterator().next().getKey());
}
@Test
public void testIntegerMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Integer, String>> type = new TypeReference<MapWrapper<Integer, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'-3':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Integer.valueOf(-3), result.map.entrySet().iterator().next().getKey());
}
@Test
public void testLongMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Long, String>> type = new TypeReference<MapWrapper<Long, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'42':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Long.valueOf(42), result.map.entrySet().iterator().next().getKey());
}
@Test
public void testFloatMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Float, String>> type = new TypeReference<MapWrapper<Float, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'3.5':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Float.valueOf(3.5f), result.map.entrySet().iterator().next().getKey());
}
@Test
public void testDoubleMapKeyDeserialization() throws Exception
{
TypeReference<MapWrapper<Double, String>> type = new TypeReference<MapWrapper<Double, String>>() { };
MapWrapper<?,?> result = MAPPER.readValue(a2q("{'map':{'0.25':'foobar'}}"), type);
assertEquals(1, result.map.size());
assertEquals(Double.valueOf(0.25), result.map.entrySet().iterator().next().getKey());
}
/*
/**********************************************************************
/* Test methods, other
/**********************************************************************
*/
@Test
public void testDeserializeKeyViaFactory() throws Exception
{
Map<FullName, Double> map =
MAPPER.readValue("{\"first.last\": 42}",
new TypeReference<Map<FullName, Double>>() { });
Map.Entry<FullName, Double> entry = map.entrySet().iterator().next();
FullName key = entry.getKey();
assertEquals(key._firstname, "first");
assertEquals(key._lastname, "last");
assertEquals(entry.getValue().doubleValue(), 42, 0);
}
@Test
public void testByteArrayMapKeyDeserialization() throws Exception
{
byte[] binary = new byte[] { 1, 2, 4, 8, 16, 33, 79 };
String encoded = Base64Variants.MIME.encode(binary);
MapWrapper<byte[], String> result = MAPPER.readValue(
a2q("{'map':{'"+encoded+"':'foobar'}}"),
new TypeReference<MapWrapper<byte[], String>>() { });
assertEquals(1, result.map.size());
Map.Entry<byte[],String> entry = result.map.entrySet().iterator().next();
assertEquals("foobar", entry.getValue());
byte[] key = entry.getKey();
assertArrayEquals(binary, key);
}
// [databind#2725]
@Test
public void testEnumWithCreatorMapKeyDeserialization() throws Exception
{
final Map<TestEnum2725, String> input = Collections.singletonMap(TestEnum2725.FOO, "Hello");
final String json = MAPPER.writeValueAsString(input);
final Map<TestEnum2725, String> output = MAPPER.readValue(json,
new TypeReference<Map<TestEnum2725, String>>() { });
assertNotNull(output);
assertEquals(1, output.size());
}
// [databind#2158]
@Test
public void testDeserializeInvalidKey() throws Exception
{
try {
MAPPER.readValue("{ \"\": 0 }", MAP_TYPE_2158);
fail("Should no pass");
} catch (InvalidFormatException e) {
verifyException(e, "Value must be nonempty");
}
}
// [databind#2158]
@Test
public void testNormalizeKey() throws Exception
{
assertEquals(Collections.singletonMap(DummyDto2158.fromValue("foo"), 0),
MAPPER.readValue("{ \"FOO\": 0 }", MAP_TYPE_2158));
}
}