JsonNodeConversionsTest.java
package com.fasterxml.jackson.databind.node;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for verifying functionality of {@link JsonNode} methods that
* convert values to other types
*/
public class JsonNodeConversionsTest extends DatabindTestUtil
{
static class Root {
public Leaf leaf;
}
static class Leaf {
public int value;
public Leaf() { }
public Leaf(int v) { value = v; }
}
@JsonDeserialize(using = LeafDeserializer.class)
public static class LeafMixIn { }
// for [databind#467]
@JsonSerialize(using=Issue467Serializer.class)
static class Issue467Bean {
public int i;
public Issue467Bean(int i0) { i = i0; }
public Issue467Bean() { this(0); }
}
@JsonSerialize(using=Issue467TreeSerializer.class)
static class Issue467Tree {
}
static class Issue467Serializer extends JsonSerializer<Issue467Bean> {
@Override
public void serialize(Issue467Bean value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
jgen.writeObject(new Issue467TmpBean(value.i));
}
}
static class Issue467TreeSerializer extends JsonSerializer<Issue467Tree> {
@Override
public void serialize(Issue467Tree value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
jgen.writeTree(BooleanNode.TRUE);
}
}
static class Issue467TmpBean {
public int x;
public Issue467TmpBean(int i) { x = i; }
}
static class Issue709Bean {
public byte[] data;
}
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="_class")
static class LongContainer1940 {
public Long longObj;
}
// [databind#433]
static class CustomSerializedPojo implements JsonSerializable
{
private final ObjectNode node = JsonNodeFactory.instance.objectNode();
public void setFoo(final String foo) {
node.put("foo", foo);
}
@Override
public void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException
{
jgen.writeTree(node);
}
@Override
public void serializeWithType(JsonGenerator g,
SerializerProvider provider, TypeSerializer typeSer) throws IOException
{
WritableTypeId typeIdDef = new WritableTypeId(this, JsonToken.START_OBJECT);
typeSer.writeTypePrefix(g, typeIdDef);
serialize(g, provider);
typeSer.writeTypePrefix(g, typeIdDef);
}
}
// [databind#4047]
@JsonRootName("event")
static class Event {
public Long id;
public String name;
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
private final ObjectMapper MAPPER = objectMapper();
@Test
public void testAsInt() throws Exception
{
assertEquals(9, IntNode.valueOf(9).asInt());
assertEquals(7, LongNode.valueOf(7L).asInt());
assertEquals(13, new TextNode("13").asInt());
assertEquals(0, new TextNode("foobar").asInt());
assertEquals(27, new TextNode("foobar").asInt(27));
assertEquals(1, BooleanNode.TRUE.asInt());
}
@Test
public void testAsBoolean() throws Exception
{
assertFalse(BooleanNode.FALSE.asBoolean());
assertTrue(BooleanNode.TRUE.asBoolean());
assertFalse(IntNode.valueOf(0).asBoolean());
assertTrue(IntNode.valueOf(1).asBoolean());
assertFalse(LongNode.valueOf(0).asBoolean());
assertTrue(LongNode.valueOf(-34L).asBoolean());
assertTrue(new TextNode("true").asBoolean());
assertFalse(new TextNode("false").asBoolean());
assertFalse(new TextNode("barf").asBoolean());
assertTrue(new TextNode("barf").asBoolean(true));
assertTrue(new POJONode(Boolean.TRUE).asBoolean());
}
// Deserializer to trigger the problem described in [JACKSON-554]
public static class LeafDeserializer extends JsonDeserializer<Leaf>
{
@Override
public Leaf deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException
{
JsonNode tree = ctxt.readTree(p);
Leaf leaf = new Leaf();
leaf.value = tree.get("value").intValue();
return leaf;
}
}
@Test
public void testTreeToValue() throws Exception
{
String JSON = "{\"leaf\":{\"value\":13}}";
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Leaf.class, LeafMixIn.class);
JsonNode root = mapper.readTree(JSON);
// Ok, try converting to bean using two mechanisms
Root r1 = mapper.treeToValue(root, Root.class);
assertNotNull(r1);
assertEquals(13, r1.leaf.value);
// ... also JavaType
r1 = mapper.treeToValue(root, mapper.constructType(Root.class));
assertEquals(13, r1.leaf.value);
// ... also TypeReference
r1 = mapper.treeToValue(root, new TypeReference<Root>() {});
assertEquals(13, r1.leaf.value);
JSON = "[{\"leaf\":{\"value\":13}}, {\"leaf\":{\"value\":12}}]";
root = mapper.readTree(JSON);
List<Root> array = mapper.treeToValue(root, new TypeReference<List<Root>>() {});
assertEquals(2, array.size());
assertEquals(13, array.get(0).leaf.value);
assertEquals(12, array.get(1).leaf.value);
}
// [databind#1208]: should coerce POJOs at least at root level
@Test
public void testTreeToValueWithPOJO() throws Exception
{
Calendar c = Calendar.getInstance();
c.setTime(new java.util.Date(0));
final ValueNode pojoNode = MAPPER.getNodeFactory().pojoNode(c);
Calendar result = MAPPER.treeToValue(pojoNode, Calendar.class);
assertEquals(result.getTimeInMillis(), c.getTimeInMillis());
// and same with JavaType
result = MAPPER.treeToValue(pojoNode, MAPPER.constructType(Calendar.class));
assertEquals(result.getTimeInMillis(), c.getTimeInMillis());
}
@Test
public void testBase64Text() throws Exception
{
// let's actually iterate over sets of encoding modes, lengths
final int[] LENS = { 1, 2, 3, 4, 7, 9, 32, 33, 34, 35 };
final Base64Variant[] VARIANTS = {
Base64Variants.MIME,
Base64Variants.MIME_NO_LINEFEEDS,
Base64Variants.MODIFIED_FOR_URL,
Base64Variants.PEM
};
for (int len : LENS) {
byte[] input = new byte[len];
for (int i = 0; i < input.length; ++i) {
input[i] = (byte) i;
}
for (Base64Variant variant : VARIANTS) {
TextNode n = new TextNode(variant.encode(input));
byte[] data = null;
try {
data = n.getBinaryValue(variant);
} catch (Exception e) {
fail("Failed (variant "+variant+", data length "+len+"): "+e.getMessage());
}
assertNotNull(data);
assertArrayEquals(data, input);
// 15-Aug-2018, tatu: [databind#2096] requires another test
JsonParser p = new TreeTraversingParser(n);
assertEquals(JsonToken.VALUE_STRING, p.nextToken());
try {
data = p.getBinaryValue(variant);
} catch (Exception e) {
fail("Failed (variant "+variant+", data length "+len+"): "+e.getMessage());
}
assertNotNull(data);
assertArrayEquals(data, input);
p.close();
}
}
}
/**
* Simple test to verify that byte[] values can be handled properly when
* converting, as long as there is metadata (from POJO definitions).
*/
@Test
public void testIssue709() throws Exception
{
byte[] inputData = new byte[] { 1, 2, 3 };
ObjectNode node = MAPPER.createObjectNode();
node.put("data", inputData);
Issue709Bean result = MAPPER.treeToValue(node, Issue709Bean.class);
String json = MAPPER.writeValueAsString(node);
Issue709Bean resultFromString = MAPPER.readValue(json, Issue709Bean.class);
Issue709Bean resultFromConvert = MAPPER.convertValue(node, Issue709Bean.class);
// all methods should work equally well:
assertArrayEquals(inputData, resultFromString.data);
assertArrayEquals(inputData, resultFromConvert.data);
assertArrayEquals(inputData, result.data);
}
@Test
public void testEmbeddedByteArray() throws Exception
{
TokenBuffer buf = new TokenBuffer(MAPPER, false);
buf.writeObject(new byte[3]);
JsonNode node = MAPPER.readTree(buf.asParser());
buf.close();
assertTrue(node.isBinary());
byte[] data = node.binaryValue();
assertNotNull(data);
assertEquals(3, data.length);
}
// [databind#232]
@Test
public void testBigDecimalAsPlainStringTreeConversion() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
Map<String, Object> map = new HashMap<String, Object>();
String PI_STR = "3.00000000";
map.put("pi", new BigDecimal(PI_STR));
JsonNode tree = mapper.valueToTree(map);
assertNotNull(tree);
assertEquals(1, tree.size());
assertTrue(tree.has("pi"));
}
// [databind#433]
@Test
public void testBeanToTree() throws Exception
{
final CustomSerializedPojo pojo = new CustomSerializedPojo();
pojo.setFoo("bar");
final JsonNode node = MAPPER.valueToTree(pojo);
assertEquals(JsonNodeType.OBJECT, node.getNodeType());
}
// [databind#467]
@Test
public void testConversionOfPojos() throws Exception
{
final Issue467Bean input = new Issue467Bean(13);
final String EXP = "{\"x\":13}";
// first, sanity check
String json = MAPPER.writeValueAsString(input);
assertEquals(EXP, json);
// then via conversions: should become JSON Object
JsonNode tree = MAPPER.valueToTree(input);
assertTrue(tree.isObject(), "Expected Object, got "+tree.getNodeType());
assertEquals(EXP, MAPPER.writeValueAsString(tree));
}
// [databind#467]
@Test
public void testConversionOfTrees() throws Exception
{
final Issue467Tree input = new Issue467Tree();
final String EXP = "true";
// first, sanity check
String json = MAPPER.writeValueAsString(input);
assertEquals(EXP, json);
// then via conversions: should become JSON Object
JsonNode tree = MAPPER.valueToTree(input);
assertTrue(tree.isBoolean(),
"Expected Object, got "+tree.getNodeType());
assertEquals(EXP, MAPPER.writeValueAsString(tree));
}
// [databind#1940]: losing of precision due to coercion
@Test
public void testBufferedLongViaCoercion() throws Exception {
long EXP = 1519348261000L;
JsonNode tree = MAPPER.readTree("{\"longObj\": "+EXP+".0, \"_class\": \""+LongContainer1940.class.getName()+"\"}");
LongContainer1940 obj = MAPPER.treeToValue(tree, LongContainer1940.class);
assertEquals(Long.valueOf(EXP), obj.longObj);
}
@Test
public void testConversionsOfNull() throws Exception
{
// First: `null` value should become `NullNode`
JsonNode n = MAPPER.valueToTree(null);
assertNotNull(n);
assertTrue(n.isNull());
// and vice versa
Object pojo = MAPPER.treeToValue(n, Root.class);
assertNull(pojo);
pojo = MAPPER.treeToValue(n, MAPPER.constructType(Root.class));
assertNull(pojo);
// [databind#2972]
AtomicReference<?> result = MAPPER.treeToValue(NullNode.instance,
AtomicReference.class);
assertNotNull(result);
assertNull(result.get());
result = MAPPER.treeToValue(NullNode.instance,
MAPPER.constructType(AtomicReference.class));
assertNotNull(result);
assertNull(result.get());
}
// Simple cast, for Tree
@Test
public void testNodeConvert() throws Exception
{
ObjectNode src = (ObjectNode) MAPPER.readTree("{}");
TreeNode node = src;
ObjectNode result = MAPPER.treeToValue(node, ObjectNode.class);
// should just cast...
assertSame(src, result);
// ditto w/ JavaType
result = MAPPER.treeToValue(node, MAPPER.constructType(ObjectNode.class));
assertSame(src, result);
}
// [databind#4047] : ObjectMapper.valueToTree will ignore the configuration SerializationFeature.WRAP_ROOT_VALUE
@Test
public void testValueToTree() throws Exception
{
// Arrange
Event value = new Event();
value.id = 1L;
value.name = "foo";
ObjectMapper wrapRootMapper = jsonMapperBuilder()
.enable(SerializationFeature.WRAP_ROOT_VALUE)
.build();
// Act & Assert
String expected = "{\"event\":{\"id\":1,\"name\":\"foo\"}}";
assertEquals(wrapRootMapper.readValue(expected, Map.class), wrapRootMapper.readValue(wrapRootMapper.writeValueAsString(value), Map.class));
assertEquals(wrapRootMapper.readValue(expected, Map.class), wrapRootMapper.readValue(wrapRootMapper.valueToTree(value).toString(), Map.class));
}
// [databind#4932]: handling of `MissingNode` wrt conversions
@Test
public void treeToValueWithMissingNode4932() throws Exception {
assertNull(MAPPER.treeToValue(MAPPER.nullNode(), Object.class));
assertNull(MAPPER.treeToValue(MAPPER.missingNode(), Object.class));
ObjectReader r = MAPPER.readerFor(Object.class);
assertNull(r.treeToValue(MAPPER.nullNode(), Object.class));
assertNull(r.treeToValue(MAPPER.missingNode(), Object.class));
}
}