diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 996398c88d..9ae2bd353c 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -15,6 +15,7 @@ Project: jackson-databind (requested by Édouard M) #2126: `DeserializationContext.instantiationException()` throws `InvalidDefinitionException` #2153: Add `JsonMapper` to replace generic `ObjectMapper` usage +#2187: Make `JsonNode.toString()` use shared `ObjectMapper` to produce valid json 2.9.8 (not yet released) diff --git a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java index 539d1dc29c..996b760fef 100644 --- a/src/main/java/com/fasterxml/jackson/databind/JsonNode.java +++ b/src/main/java/com/fasterxml/jackson/databind/JsonNode.java @@ -947,10 +947,10 @@ public boolean equals(Comparator comparator, JsonNode other) { */ /** - * Method that will produce developer-readable representation of the - * node; which may or may not be as valid JSON. - * If you want valid JSON output (or output formatted using one of - * other Jackson supported data formats) make sure to use + * Method that will produce (as of Jackson 2.10) valid JSON using + * default settings of databind, as String. + * If you want other kinds of JSON output (or output formatted using one of + * other Jackson-supported data formats) make sure to use * {@link ObjectMapper} or {@link ObjectWriter} to serialize an * instance, for example: *
@@ -964,6 +964,16 @@ public boolean equals(Comparator comparator, JsonNode other) {
     @Override
     public abstract String toString();
 
+    /**
+     * Alternative to {@link #toString} that will serialize this node using
+     * Jackson default pretty-printer.
+     *
+     * @since 2.10
+     */
+    public String toPrettyString() {
+        return toString();
+    }
+    
     /**
      * Equality for node objects is defined as full (deep) value
      * equality. This means that it is possible to compare complete
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
index 254daa38d5..20307c7754 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java
@@ -839,21 +839,6 @@ public int hashCode() {
         return _children.hashCode();
     }
 
-    @Override
-    public String toString()
-    {
-        StringBuilder sb = new StringBuilder(16 + (size() << 4));
-        sb.append('[');
-        for (int i = 0, len = _children.size(); i < len; ++i) {
-            if (i > 0) {
-                sb.append(',');
-            }
-            sb.append(_children.get(i).toString());
-        }
-        sb.append(']');
-        return sb.toString();
-    }
-
     /*
     /**********************************************************
     /* Internal methods (overridable)
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java b/src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java
index d0d38dd4a5..8f936ed582 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java
@@ -97,5 +97,21 @@ public abstract void serialize(JsonGenerator jgen, SerializerProvider provider)
     public abstract void serializeWithType(JsonGenerator jgen, SerializerProvider provider,
             TypeSerializer typeSer)
         throws IOException, JsonProcessingException;
+
+   /*
+   /**********************************************************
+   /* Std method overrides
+   /**********************************************************
+    */
+
+   @Override
+   public final String toString() {
+       return InternalNodeMapper.nodeToString(this);
+   }
+
+   @Override
+   public final String toPrettyString() {
+       return InternalNodeMapper.nodeToPrettyString(this);
+   }
 }
 
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/BinaryNode.java b/src/main/java/com/fasterxml/jackson/databind/node/BinaryNode.java
index 858d56c7bb..f87b5705c5 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/BinaryNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/BinaryNode.java
@@ -110,14 +110,4 @@ public boolean equals(Object o)
     public int hashCode() {
         return (_data == null) ? -1 : _data.length;
     }
-
-    /**
-     * Different from other values, since contents need to be surrounded
-     * by (double) quotes.
-     */
-    @Override
-    public String toString()
-    {
-        return Base64Variants.getDefaultVariant().encode(_data, true);
-    }
 }
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/InternalNodeMapper.java b/src/main/java/com/fasterxml/jackson/databind/node/InternalNodeMapper.java
new file mode 100644
index 0000000000..d602f01631
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/node/InternalNodeMapper.java
@@ -0,0 +1,37 @@
+package com.fasterxml.jackson.databind.node;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+
+/**
+ * Helper class used to implement toString() method for
+ * {@link BaseJsonNode}, by embedding a private instance of
+ * {@link JsonMapper}, only to be used for node serialization.
+ *
+ * @since 2.10 (but not to be included in 3.0)
+ */
+final class InternalNodeMapper {
+    private final static JsonMapper JSON_MAPPER = new JsonMapper();
+    private final static ObjectWriter STD_WRITER = JSON_MAPPER.writer();
+    private final static ObjectWriter PRETTY_WRITER = JSON_MAPPER.writer()
+            .withDefaultPrettyPrinter();
+
+    public static String nodeToString(JsonNode n) {
+        try {
+            return STD_WRITER.writeValueAsString(n);
+        } catch (IOException e) { // should never occur
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String nodeToPrettyString(JsonNode n) {
+        try {
+            return PRETTY_WRITER.writeValueAsString(n);
+        } catch (IOException e) { // should never occur
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java b/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java
index 65509fe585..acb6a916c0 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/MissingNode.java
@@ -98,12 +98,6 @@ public boolean equals(Object o)
         return (o == this);
     }
 
-    @Override
-    public String toString() {
-        // toString() should never return null
-        return "";
-    }
-
     @Override
     public int hashCode() {
         return JsonNodeType.MISSING.ordinal();
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
index c80d3addee..06b48b4ca1 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java
@@ -852,25 +852,6 @@ public int hashCode()
         return _children.hashCode();
     }
 
-    @Override
-    public String toString()
-    {
-        StringBuilder sb = new StringBuilder(32 + (size() << 4));
-        sb.append("{");
-        int count = 0;
-        for (Map.Entry en : _children.entrySet()) {
-            if (count > 0) {
-                sb.append(",");
-            }
-            ++count;
-            TextNode.appendQuoted(sb, en.getKey());
-            sb.append(':');
-            sb.append(en.getValue().toString());
-        }
-        sb.append("}");
-        return sb.toString();
-    }
-
     /*
     /**********************************************************
     /* Internal methods (overridable)
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java b/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java
index 06c315cfb8..c8feff0267 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/POJONode.java
@@ -5,7 +5,6 @@
 import com.fasterxml.jackson.core.*;
 import com.fasterxml.jackson.databind.JsonSerializable;
 import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.util.RawValue;
 
 /**
  * Value node that contains a wrapped POJO, to be serialized as
@@ -156,17 +155,4 @@ protected boolean _pojoEquals(POJONode other)
     
     @Override
     public int hashCode() { return _value.hashCode(); }
-
-    @Override
-    public String toString()
-    {
-        // [databind#743]: Let's try indicating content type, for debugging purposes
-        if (_value instanceof byte[]) {
-            return String.format("(binary value of %d bytes)", ((byte[]) _value).length);
-        }
-        if (_value instanceof RawValue) {
-            return String.format("(raw value '%s')", ((RawValue) _value).toString());
-        }
-        return String.valueOf(_value);
-    }
 }
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java b/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java
index 26a7f916d8..9c0cd54313 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/TextNode.java
@@ -164,19 +164,7 @@ public boolean equals(Object o)
     @Override
     public int hashCode() { return _value.hashCode(); }
 
-    /**
-     * Different from other values, Strings need quoting
-     */
-    @Override
-    public String toString()
-    {
-        int len = _value.length();
-        len = len + 2 + (len >> 4);
-        StringBuilder sb = new StringBuilder(len);
-        appendQuoted(sb, _value);
-        return sb.toString();
-    }
-
+    @Deprecated // since 2.10
     protected static void appendQuoted(StringBuilder sb, String content)
     {
         sb.append('"');
diff --git a/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java b/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java
index 363d59e847..93d7f4af70 100644
--- a/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java
+++ b/src/main/java/com/fasterxml/jackson/databind/node/ValueNode.java
@@ -47,15 +47,6 @@ public void serializeWithType(JsonGenerator g, SerializerProvider provider,
         typeSer.writeTypeSuffix(g, typeIdDef);
     }
 
-    /*
-    /**********************************************************************
-    /* Base impls for standard methods
-    /**********************************************************************
-     */
-
-    @Override
-    public String toString() { return asText(); }
-
     /*
      **********************************************************************
      * Navigation methods
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java
index cd6d34ed0d..52e51668cc 100644
--- a/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/deftyping/TestDefaultForArrays.java
@@ -8,6 +8,7 @@
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.databind.*;
 import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 
 public class TestDefaultForArrays extends BaseMapTest
 {
@@ -95,7 +96,11 @@ public void testNodeInEmptyArray() throws Exception {
         Object[] obs = new Object[] { node };
         json = m.writeValueAsString(obs);
         Object[] result = m.readValue(json, Object[].class);
-        assertEquals("{}", result[0].toString());
+
+        assertEquals(1, result.length);
+        Object elem = result[0];
+        assertTrue(elem instanceof ObjectNode);
+        assertEquals(0, ((ObjectNode) elem).size());
     }
 
     public void testArraysOfArrays() throws Exception
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
index b617d68b06..2efa52e095 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/ObjectNodeTest.java
@@ -328,7 +328,6 @@ public void testInvalidWithArray() throws Exception
         }
     }
 
-    // [Issue#93]
     public void testSetAll() throws Exception
     {
         ObjectNode root = MAPPER.createObjectNode();
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java
index 0750fef6fd..1741d4b7a5 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestJsonNode.java
@@ -83,8 +83,8 @@ public void testPOJO()
         assertStandardEquals(n);
         assertEquals(n, new POJONode("x"));
         assertEquals("x", n.asText());
-        // not sure if this is what it'll remain as but:
-        assertEquals("x", n.toString());
+        // 10-Dec-2018, tatu: With 2.10, should serialize same as via ObjectMapper/ObjectWriter
+        assertEquals("\"x\"", n.toString());
 
         assertEquals(new POJONode(null), new POJONode(null));
 
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java b/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java
index 45f0bac7d8..01448ef950 100644
--- a/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java
+++ b/src/test/java/com/fasterxml/jackson/databind/node/TestMissingNode.java
@@ -15,7 +15,8 @@ public void testMissing()
         assertEquals(JsonToken.NOT_AVAILABLE, n.asToken());
         assertEquals("", n.asText());
         assertStandardEquals(n);
-        assertEquals("", n.toString());
+        // 10-Dec-2018, tatu: With 2.10, should serialize same as via ObjectMapper/ObjectWriter
+        assertEquals("null", n.toString());
 
         assertNodeNumbersForNonNumeric(n);
 
diff --git a/src/test/java/com/fasterxml/jackson/databind/node/ToStringForNodesTest.java b/src/test/java/com/fasterxml/jackson/databind/node/ToStringForNodesTest.java
new file mode 100644
index 0000000000..2a1c4009ec
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/node/ToStringForNodesTest.java
@@ -0,0 +1,37 @@
+package com.fasterxml.jackson.databind.node;
+
+import com.fasterxml.jackson.databind.*;
+
+public class ToStringForNodesTest extends BaseMapTest
+{
+    private final ObjectMapper MAPPER = objectMapper();
+
+    public void testObjectNode() throws Exception
+    {
+        _verifyToStrings(MAPPER.readTree("{ \"key\" : 1, \"b\" : \"x\", \"array\" : [ 1, false ] }"));
+        final ObjectNode n = MAPPER.createObjectNode().put("msg", "hello world");
+        assertEquals("{\"msg\":\"hello world\"}", n.toString());
+        assertEquals("{\n  \"msg\" : \"hello world\"\n}", n.toPrettyString());
+    }
+
+    public void testArrayNode() throws Exception
+    {
+        _verifyToStrings(MAPPER.readTree("[ 1, true, null, [ \"abc\",3], { } ]"));
+        final ArrayNode n = MAPPER.createArrayNode().add(0.25).add(true);
+        assertEquals("[0.25,true]", n.toString());
+        assertEquals("[ 0.25, true ]", n.toPrettyString());
+    }
+
+    public void testBinaryNode() throws Exception
+    {
+        _verifyToStrings(MAPPER.getNodeFactory().binaryNode(new byte[] { 1, 2, 3, 4, 6 }));
+    }
+    
+    protected void _verifyToStrings(JsonNode node) throws Exception
+    {
+        assertEquals(MAPPER.writeValueAsString(node), node.toString());
+
+        assertEquals(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(node),
+                node.toPrettyString());
+    }
+}