package com.fasterxml.jackson.databind.node; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.exc.MismatchedInputException; /** * Additional tests for {@link ObjectNode} container class. */ public class ObjectNodeTest extends BaseMapTest { @JsonDeserialize(as = DataImpl.class) public interface Data { } public static class DataImpl implements Data { protected JsonNode root; @JsonCreator public DataImpl(JsonNode n) { root = n; } @JsonValue public JsonNode value() { return root; } /* public Wrapper(ObjectNode n) { root = n; } @JsonValue public ObjectNode value() { return root; } */ } static class ObNodeWrapper { @JsonInclude(JsonInclude.Include.NON_EMPTY) public ObjectNode node; public ObNodeWrapper(ObjectNode n) { node = n; } } // [databind#941] static class MyValue { private final ObjectNode object; @JsonCreator public MyValue(ObjectNode object) { this.object = object; } @JsonValue public ObjectNode getObject() { return object; } } /* /********************************************************** /* Test methods /********************************************************** */ private final ObjectMapper MAPPER = objectMapper(); public void testSimpleObject() throws Exception { String JSON = "{ \"key\" : 1, \"b\" : \"x\" }"; JsonNode root = MAPPER.readTree(JSON); // basic properties first: assertFalse(root.isValueNode()); assertTrue(root.isContainerNode()); assertFalse(root.isArray()); assertTrue(root.isObject()); assertEquals(2, root.size()); Iterator<JsonNode> it = root.iterator(); assertNotNull(it); assertTrue(it.hasNext()); JsonNode n = it.next(); assertNotNull(n); assertEquals(IntNode.valueOf(1), n); assertTrue(it.hasNext()); n = it.next(); assertNotNull(n); assertEquals(TextNode.valueOf("x"), n); assertFalse(it.hasNext()); // Ok, then, let's traverse via extended interface ObjectNode obNode = (ObjectNode) root; Iterator<Map.Entry<String,JsonNode>> fit = obNode.fields(); // we also know that LinkedHashMap is used, i.e. order preserved assertTrue(fit.hasNext()); Map.Entry<String,JsonNode> en = fit.next(); assertEquals("key", en.getKey()); assertEquals(IntNode.valueOf(1), en.getValue()); assertTrue(fit.hasNext()); en = fit.next(); assertEquals("b", en.getKey()); assertEquals(TextNode.valueOf("x"), en.getValue()); // Plus: we should be able to modify the node via iterator too: fit.remove(); assertEquals(1, obNode.size()); assertEquals(IntNode.valueOf(1), root.get("key")); assertNull(root.get("b")); } // for [databind#346] public void testEmptyNodeAsValue() throws Exception { Data w = MAPPER.readValue("{}", Data.class); assertNotNull(w); } public void testBasics() { ObjectNode n = new ObjectNode(JsonNodeFactory.instance); assertStandardEquals(n); assertFalse(n.elements().hasNext()); assertFalse(n.fields().hasNext()); assertFalse(n.fieldNames().hasNext()); assertNull(n.get("a")); assertTrue(n.path("a").isMissingNode()); TextNode text = TextNode.valueOf("x"); assertSame(n, n.set("a", text)); assertEquals(1, n.size()); assertTrue(n.elements().hasNext()); assertTrue(n.fields().hasNext()); assertTrue(n.fieldNames().hasNext()); assertSame(text, n.get("a")); assertSame(text, n.path("a")); assertNull(n.get("b")); assertNull(n.get(0)); // not used with objects assertFalse(n.has(0)); assertFalse(n.hasNonNull(0)); assertTrue(n.has("a")); assertTrue(n.hasNonNull("a")); assertFalse(n.has("b")); assertFalse(n.hasNonNull("b")); ObjectNode n2 = new ObjectNode(JsonNodeFactory.instance); n2.put("b", 13); assertFalse(n.equals(n2)); n.setAll(n2); assertEquals(2, n.size()); n.set("null", (JsonNode)null); assertEquals(3, n.size()); // may be non-intuitive, but explicit nulls do exist in tree: assertTrue(n.has("null")); assertFalse(n.hasNonNull("null")); // should replace, not add n.put("null", "notReallNull"); assertEquals(3, n.size()); assertNotNull(n.remove("null")); assertEquals(2, n.size()); Map<String,JsonNode> nodes = new HashMap<String,JsonNode>(); nodes.put("d", text); n.setAll(nodes); assertEquals(3, n.size()); n.removeAll(); assertEquals(0, n.size()); } public void testBigNumbers() { ObjectNode n = new ObjectNode(JsonNodeFactory.instance); assertStandardEquals(n); BigInteger I = BigInteger.valueOf(3); BigDecimal DEC = new BigDecimal("0.1"); n.put("a", DEC); n.put("b", I); assertEquals(2, n.size()); assertTrue(n.path("a").isBigDecimal()); assertEquals(DEC, n.get("a").decimalValue()); assertTrue(n.path("b").isBigInteger()); assertEquals(I, n.get("b").bigIntegerValue()); } /** * Verify null handling */ public void testNullChecking() { ObjectNode o1 = JsonNodeFactory.instance.objectNode(); ObjectNode o2 = JsonNodeFactory.instance.objectNode(); // used to throw NPE before fix: o1.setAll(o2); assertEquals(0, o1.size()); assertEquals(0, o2.size()); // also: nulls should be converted to NullNodes... o1.set("x", null); JsonNode n = o1.get("x"); assertNotNull(n); assertSame(n, NullNode.instance); o1.put("str", (String) null); n = o1.get("str"); assertNotNull(n); assertSame(n, NullNode.instance); o1.put("d", (BigDecimal) null); n = o1.get("d"); assertNotNull(n); assertSame(n, NullNode.instance); o1.put("3", (BigInteger) null); n = o1.get("3"); assertNotNull(3); assertSame(n, NullNode.instance); assertEquals(4, o1.size()); } /** * Another test to verify [JACKSON-227]... */ public void testNullChecking2() { ObjectNode src = MAPPER.createObjectNode(); ObjectNode dest = MAPPER.createObjectNode(); src.put("a", "b"); dest.setAll(src); } public void testRemove() { ObjectNode ob = MAPPER.createObjectNode(); ob.put("a", "a"); ob.put("b", "b"); ob.put("c", "c"); assertEquals(3, ob.size()); assertSame(ob, ob.without(Arrays.asList("a", "c"))); assertEquals(1, ob.size()); assertEquals("b", ob.get("b").textValue()); } public void testRetain() { ObjectNode ob = MAPPER.createObjectNode(); ob.put("a", "a"); ob.put("b", "b"); ob.put("c", "c"); assertEquals(3, ob.size()); assertSame(ob, ob.retain("a", "c")); assertEquals(2, ob.size()); assertEquals("a", ob.get("a").textValue()); assertNull(ob.get("b")); assertEquals("c", ob.get("c").textValue()); } public void testValidWith() throws Exception { ObjectNode root = MAPPER.createObjectNode(); assertEquals("{}", MAPPER.writeValueAsString(root)); JsonNode child = root.with("prop"); assertTrue(child instanceof ObjectNode); assertEquals("{\"prop\":{}}", MAPPER.writeValueAsString(root)); } public void testValidWithArray() throws Exception { ObjectNode root = MAPPER.createObjectNode(); assertEquals("{}", MAPPER.writeValueAsString(root)); JsonNode child = root.withArray("arr"); assertTrue(child instanceof ArrayNode); assertEquals("{\"arr\":[]}", MAPPER.writeValueAsString(root)); } public void testInvalidWith() throws Exception { JsonNode root = MAPPER.createArrayNode(); try { // should not work for non-ObjectNode nodes: root.with("prop"); fail("Expected exception"); } catch (UnsupportedOperationException e) { verifyException(e, "not of type ObjectNode"); } // also: should fail of we already have non-object property ObjectNode root2 = MAPPER.createObjectNode(); root2.put("prop", 13); try { // should not work for non-ObjectNode nodes: root2.with("prop"); fail("Expected exception"); } catch (UnsupportedOperationException e) { verifyException(e, "has value that is not"); } } public void testInvalidWithArray() throws Exception { JsonNode root = MAPPER.createArrayNode(); try { // should not work for non-ObjectNode nodes: root.withArray("prop"); fail("Expected exception"); } catch (UnsupportedOperationException e) { verifyException(e, "not of type ObjectNode"); } // also: should fail of we already have non-Array property ObjectNode root2 = MAPPER.createObjectNode(); root2.put("prop", 13); try { // should not work for non-ObjectNode nodes: root2.withArray("prop"); fail("Expected exception"); } catch (UnsupportedOperationException e) { verifyException(e, "has value that is not"); } } // [Issue#93] public void testSetAll() throws Exception { ObjectNode root = MAPPER.createObjectNode(); assertEquals(0, root.size()); HashMap<String,JsonNode> map = new HashMap<String,JsonNode>(); map.put("a", root.numberNode(1)); root.setAll(map); assertEquals(1, root.size()); assertTrue(root.has("a")); assertFalse(root.has("b")); map.put("b", root.numberNode(2)); root.setAll(map); assertEquals(2, root.size()); assertTrue(root.has("a")); assertTrue(root.has("b")); assertEquals(2, root.path("b").intValue()); // Then with ObjectNodes... ObjectNode root2 = MAPPER.createObjectNode(); root2.setAll(root); assertEquals(2, root.size()); assertEquals(2, root2.size()); root2.setAll(root); assertEquals(2, root.size()); assertEquals(2, root2.size()); ObjectNode root3 = MAPPER.createObjectNode(); root3.put("a", 2); root3.put("c", 3); assertEquals(2, root3.path("a").intValue()); root3.setAll(root2); assertEquals(3, root3.size()); assertEquals(1, root3.path("a").intValue()); } // [databind#237] (databind): support DeserializationFeature#FAIL_ON_READING_DUP_TREE_KEY public void testFailOnDupKeys() throws Exception { final String DUP_JSON = "{ \"a\":1, \"a\":2 }"; // first: verify defaults: ObjectMapper mapper = new ObjectMapper(); assertFalse(mapper.isEnabled(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY)); ObjectNode root = (ObjectNode) mapper.readTree(DUP_JSON); assertEquals(2, root.path("a").asInt()); // and then enable checks: try { mapper.reader(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY).readTree(DUP_JSON); fail("Should have thrown exception!"); } catch (JsonMappingException e) { verifyException(e, "duplicate field 'a'"); } } public void testEqualityWrtOrder() throws Exception { ObjectNode ob1 = MAPPER.createObjectNode(); ObjectNode ob2 = MAPPER.createObjectNode(); // same contents, different insertion order; should not matter ob1.put("a", 1); ob1.put("b", 2); ob1.put("c", 3); ob2.put("b", 2); ob2.put("c", 3); ob2.put("a", 1); assertTrue(ob1.equals(ob2)); assertTrue(ob2.equals(ob1)); } public void testSimplePath() throws Exception { JsonNode root = MAPPER.readTree("{ \"results\" : { \"a\" : 3 } }"); assertTrue(root.isObject()); JsonNode rnode = root.path("results"); assertNotNull(rnode); assertTrue(rnode.isObject()); assertEquals(3, rnode.path("a").intValue()); } public void testNonEmptySerialization() throws Exception { ObNodeWrapper w = new ObNodeWrapper(MAPPER.createObjectNode() .put("a", 3)); assertEquals("{\"node\":{\"a\":3}}", MAPPER.writeValueAsString(w)); w = new ObNodeWrapper(MAPPER.createObjectNode()); assertEquals("{}", MAPPER.writeValueAsString(w)); } public void testIssue941() throws Exception { ObjectNode object = MAPPER.createObjectNode(); String json = MAPPER.writeValueAsString(object); // System.out.println("json: "+json); ObjectNode de1 = MAPPER.readValue(json, ObjectNode.class); // this works // System.out.println("Deserialized to ObjectNode: "+de1); assertNotNull(de1); MyValue de2 = MAPPER.readValue(json, MyValue.class); // but this throws exception // System.out.println("Deserialized to MyValue: "+de2); assertNotNull(de2); } public void testSimpleMismatch() throws Exception { ObjectMapper mapper = objectMapper(); try { mapper.readValue("[ 1, 2, 3 ]", ObjectNode.class); fail("Should not pass"); } catch (MismatchedInputException e) { verifyException(e, "out of START_ARRAY token"); } } }