Skip to content

Commit 92ca9f4

Browse files
committed
Fixed #18
1 parent efa6044 commit 92ca9f4

File tree

9 files changed

+159
-12
lines changed

9 files changed

+159
-12
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Project: jackson-databind
66

77
2.10.0 (not yet released)
88

9+
#18: Make `ObjectNode` and `ArrayNode` serializable
910
#1675: Remove "impossible" `IOException` in `readTree()` and `readValue()` `ObjectMapper`
1011
methods which accept Strings
1112
(requested by matthew-pwnieexpress@github)

src/main/java/com/fasterxml/jackson/databind/node/ArrayNode.java

+14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
*/
2424
public class ArrayNode
2525
extends ContainerNode<ArrayNode>
26+
implements java.io.Serializable // since 2.10
2627
{
28+
private static final long serialVersionUID = 1L;
29+
2730
private final List<JsonNode> _children;
2831

2932
public ArrayNode(JsonNodeFactory nf) {
@@ -842,6 +845,17 @@ public int hashCode() {
842845
return _children.hashCode();
843846
}
844847

848+
/*
849+
/**********************************************************
850+
/* JDK Serialization support
851+
/**********************************************************
852+
*/
853+
854+
// Simplest way is by using a helper
855+
Object writeReplace() {
856+
return NodeSerialization.from(this);
857+
}
858+
845859
/*
846860
/**********************************************************
847861
/* Internal methods (overridable)

src/main/java/com/fasterxml/jackson/databind/node/BaseJsonNode.java

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
*/
1818
public abstract class BaseJsonNode
1919
extends JsonNode
20-
implements JsonSerializable
2120
{
2221
protected BaseJsonNode() { }
2322

src/main/java/com/fasterxml/jackson/databind/node/ContainerNode.java

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ protected ContainerNode(JsonNodeFactory nc) {
2626
_nodeFactory = nc;
2727
}
2828

29+
protected ContainerNode() { _nodeFactory = null; } // only for JDK ser
30+
2931
// all containers are mutable: can't define:
3032
// @Override public abstract <T extends JsonNode> T deepCopy();
3133

src/main/java/com/fasterxml/jackson/databind/node/InternalNodeMapper.java

+16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44

55
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.ObjectReader;
67
import com.fasterxml.jackson.databind.ObjectWriter;
78
import com.fasterxml.jackson.databind.json.JsonMapper;
89

@@ -15,10 +16,15 @@
1516
*/
1617
final class InternalNodeMapper {
1718
private final static JsonMapper JSON_MAPPER = new JsonMapper();
19+
1820
private final static ObjectWriter STD_WRITER = JSON_MAPPER.writer();
1921
private final static ObjectWriter PRETTY_WRITER = JSON_MAPPER.writer()
2022
.withDefaultPrettyPrinter();
2123

24+
private final static ObjectReader NODE_READER = JSON_MAPPER.readerFor(JsonNode.class);
25+
26+
// // // Methods for `JsonNode.toString()` and `JsonNode.toPrettyString()`
27+
2228
public static String nodeToString(JsonNode n) {
2329
try {
2430
return STD_WRITER.writeValueAsString(n);
@@ -34,4 +40,14 @@ public static String nodeToPrettyString(JsonNode n) {
3440
throw new RuntimeException(e);
3541
}
3642
}
43+
44+
// // // Methods for JDK serialization support of JsonNodes
45+
46+
public static byte[] valueToBytes(Object value) throws IOException {
47+
return JSON_MAPPER.writeValueAsBytes(value);
48+
}
49+
50+
public static JsonNode bytesToNode(byte[] json) throws IOException {
51+
return NODE_READER.readValue(json);
52+
}
3753
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.fasterxml.jackson.databind.node;
2+
3+
import java.io.IOException;
4+
5+
/**
6+
* Helper value class only used during JDK serialization: contains JSON as `byte[]`
7+
*
8+
* @since 2.10
9+
*/
10+
class NodeSerialization implements java.io.Serializable {
11+
private static final long serialVersionUID = 1L;
12+
13+
public byte[] json;
14+
15+
public NodeSerialization(byte[] b) { json = b; }
16+
17+
protected Object readResolve() {
18+
try {
19+
return InternalNodeMapper.bytesToNode(json);
20+
} catch (IOException e) {
21+
throw new IllegalArgumentException("Failed to JDK deserialize `JsonNode` value: "+e.getMessage(), e);
22+
}
23+
}
24+
25+
public static NodeSerialization from(Object o) {
26+
try {
27+
return new NodeSerialization(InternalNodeMapper.valueToBytes(o));
28+
} catch (IOException e) {
29+
throw new IllegalArgumentException("Failed to JDK serialize `"+o.getClass().getSimpleName()+"` value: "+e.getMessage(), e);
30+
}
31+
}
32+
}

src/main/java/com/fasterxml/jackson/databind/node/ObjectNode.java

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
package com.fasterxml.jackson.databind.node;
22

3+
import java.io.*;
4+
import java.math.BigDecimal;
5+
import java.math.BigInteger;
6+
import java.util.*;
7+
38
import com.fasterxml.jackson.core.*;
49
import com.fasterxml.jackson.core.type.WritableTypeId;
510
import com.fasterxml.jackson.databind.*;
611
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
712
import com.fasterxml.jackson.databind.util.RawValue;
813

9-
import java.io.IOException;
10-
import java.math.BigDecimal;
11-
import java.math.BigInteger;
12-
import java.util.*;
13-
1414
/**
1515
* Node that maps to JSON Object structures in JSON content.
1616
*<p>
1717
* Note: class was <code>final</code> temporarily for Jackson 2.2.
1818
*/
1919
public class ObjectNode
2020
extends ContainerNode<ObjectNode>
21+
implements java.io.Serializable
2122
{
23+
private static final long serialVersionUID = 1L; // since 2.10
24+
2225
// Note: LinkedHashMap for backwards compatibility
2326
protected final Map<String, JsonNode> _children;
2427

@@ -872,6 +875,17 @@ public int hashCode()
872875
return _children.hashCode();
873876
}
874877

878+
/*
879+
/**********************************************************
880+
/* JDK Serialization support
881+
/**********************************************************
882+
*/
883+
884+
// Simplest way is by using a helper
885+
Object writeReplace() {
886+
return NodeSerialization.from(this);
887+
}
888+
875889
/*
876890
/**********************************************************
877891
/* Internal methods (overridable)

src/test/java/com/fasterxml/jackson/databind/TestJDKSerialization.java

-6
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@
55

66
import com.fasterxml.jackson.annotation.JsonAnyGetter;
77
import com.fasterxml.jackson.annotation.JsonAnySetter;
8-
import com.fasterxml.jackson.databind.DeserializationConfig;
9-
import com.fasterxml.jackson.databind.JavaType;
10-
import com.fasterxml.jackson.databind.ObjectMapper;
11-
import com.fasterxml.jackson.databind.ObjectReader;
12-
import com.fasterxml.jackson.databind.ObjectWriter;
13-
import com.fasterxml.jackson.databind.SerializationConfig;
148
import com.fasterxml.jackson.databind.type.TypeFactory;
159
import com.fasterxml.jackson.databind.util.LRUMap;
1610

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.fasterxml.jackson.databind;
2+
3+
import java.io.*;
4+
5+
import com.fasterxml.jackson.databind.node.ArrayNode;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
7+
8+
public class TestNodeJDKSerialization extends BaseMapTest
9+
{
10+
private final ObjectMapper MAPPER = newObjectMapper();
11+
12+
/*
13+
/**********************************************************
14+
/* Then something bit different; serialize `JsonNode`(s)
15+
/**********************************************************
16+
*/
17+
18+
// [databind#18]: Allow JDK serialization of `ObjectNode`
19+
public void testObjectNodeSerialization() throws Exception
20+
{
21+
ObjectNode root = MAPPER.createObjectNode();
22+
root.put("answer", 42);
23+
ArrayNode arr = root.withArray("matrix");
24+
arr.add(1).add(12345678901L).add(true).add("...");
25+
ObjectNode misc = root.with("misc");
26+
misc.put("value", 0.25);
27+
28+
byte[] ser = jdkSerialize(root);
29+
JsonNode result = jdkDeserialize(ser);
30+
assertEquals(root, result);
31+
}
32+
33+
// [databind#18]: Allow JDK serialization of `ArrayNode`
34+
public void testArrayNodeSerialization() throws Exception
35+
{
36+
ArrayNode root = MAPPER.createArrayNode();
37+
root.add(false);
38+
ObjectNode props = root.addObject();
39+
props.put("answer", 42);
40+
root.add(137);
41+
42+
byte[] ser = jdkSerialize(root);
43+
JsonNode result = jdkDeserialize(ser);
44+
assertEquals(root, result);
45+
}
46+
47+
/*
48+
/**********************************************************
49+
/* Helper methods
50+
/**********************************************************
51+
*/
52+
53+
protected byte[] jdkSerialize(Object o) throws IOException
54+
{
55+
ByteArrayOutputStream bytes = new ByteArrayOutputStream(1000);
56+
ObjectOutputStream obOut = new ObjectOutputStream(bytes);
57+
obOut.writeObject(o);
58+
obOut.close();
59+
return bytes.toByteArray();
60+
}
61+
62+
@SuppressWarnings("unchecked")
63+
protected <T> T jdkDeserialize(byte[] raw) throws IOException
64+
{
65+
ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(raw));
66+
try {
67+
return (T) objIn.readObject();
68+
} catch (ClassNotFoundException e) {
69+
fail("Missing class: "+e.getMessage());
70+
return null;
71+
} finally {
72+
objIn.close();
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)