How to serialize DefaultMutableTreeNode (Java) to JSON?
How can I serialize a tree (implemented in Java using the DefaultMutableTreeNode
class) to JSON (for transferring via RESTful method to an iOS client)?
I tried:
String jsonString = (new Gson()).toJson(topNode);
// topNode is DefaultMutableTreeNode at the root
It crashed with StackOverflowError
.
java json serialization gson jtree
add a comment |
How can I serialize a tree (implemented in Java using the DefaultMutableTreeNode
class) to JSON (for transferring via RESTful method to an iOS client)?
I tried:
String jsonString = (new Gson()).toJson(topNode);
// topNode is DefaultMutableTreeNode at the root
It crashed with StackOverflowError
.
java json serialization gson jtree
add a comment |
How can I serialize a tree (implemented in Java using the DefaultMutableTreeNode
class) to JSON (for transferring via RESTful method to an iOS client)?
I tried:
String jsonString = (new Gson()).toJson(topNode);
// topNode is DefaultMutableTreeNode at the root
It crashed with StackOverflowError
.
java json serialization gson jtree
How can I serialize a tree (implemented in Java using the DefaultMutableTreeNode
class) to JSON (for transferring via RESTful method to an iOS client)?
I tried:
String jsonString = (new Gson()).toJson(topNode);
// topNode is DefaultMutableTreeNode at the root
It crashed with StackOverflowError
.
java json serialization gson jtree
java json serialization gson jtree
edited Jan 2 at 1:13
Thomas Fritsch
5,436122135
5,436122135
asked Jan 1 at 16:33
ikevin8meikevin8me
1,94722960
1,94722960
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
Swing's DefaultMutableTreeNode
class is a tree-like data structure
which contains instances of this same type both as children
and as parent
.
That's why Gson's default serializer ran into infinite recursion
and hence threw a StackOverflowError
.
To solve this problem you need to customize your Gson
with a smarter JsonSerializer
specially crafted for converting a DefaultMutableTreeNode
to JSON.
As a bonus you might also want to provide a JsonDeserializer
for converting such JSON back to a DefaultMutableTreeNode
.
For that create your Gson
instance not just by new Gson()
, but by
Gson gson = new GsonBuilder()
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeSerializer())
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeDeserializer())
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeSerializer
below is responsible
for converting a DefaultMutableTreeNode
to JSON.
It converts its properties allowsChildren
, userObject
and children
to JSON.
Note that it does not convert the parent
property to JSON,
because doing that would produce an inifinite recursion again.
public class DefaultMutableTreeNodeSerializer implements JsonSerializer<DefaultMutableTreeNode> {
@Override
public JsonElement serialize(DefaultMutableTreeNode src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("allowsChildren", src.getAllowsChildren());
jsonObject.add("userObject", context.serialize(src.getUserObject()));
if (src.getChildCount() > 0) {
jsonObject.add("children", context.serialize(Collections.list(src.children())));
}
return jsonObject;
}
}
For testing let us serialize the root node of a sample JTree
to JSON,
and then deserialize it again.
JTree tree = new JTree(); // create a sample tree
Object topNode = tree.getModel().getRoot(); // a DefaultMutableTreeNode
String jsonString = gson.toJson(topNode);
System.out.println(jsonString);
DefaultMutableTreeNode topNode2 = gson.fromJson(jsonString, DefaultMutableTreeNode.class);
It generates the following JSON output:
{
"allowsChildren": true,
"userObject": "JTree",
"children": [
{
"allowsChildren": true,
"userObject": "colors",
"children": [
{
"allowsChildren": true,
"userObject": "blue"
},
{
"allowsChildren": true,
"userObject": "violet"
},
{
"allowsChildren": true,
"userObject": "red"
},
{
"allowsChildren": true,
"userObject": "yellow"
}
]
},
{
"allowsChildren": true,
"userObject": "sports",
"children": [
{
"allowsChildren": true,
"userObject": "basketball"
},
{
"allowsChildren": true,
"userObject": "soccer"
},
{
"allowsChildren": true,
"userObject": "football"
},
{
"allowsChildren": true,
"userObject": "hockey"
}
]
},
{
"allowsChildren": true,
"userObject": "food",
"children": [
{
"allowsChildren": true,
"userObject": "hot dogs"
},
{
"allowsChildren": true,
"userObject": "pizza"
},
{
"allowsChildren": true,
"userObject": "ravioli"
},
{
"allowsChildren": true,
"userObject": "bananas"
}
]
}
]
}
The DefaultMutableTreeNodeDeserializer
below is responsible
for converting JSON back to a DefaultMutableTreeNode
.
It uses the same idea as the deserializer from
How to serialize/deserialize a DefaultMutableTreeNode with Jackson?.
The DefaultMutableTreeNode
is not very POJO-like and thus doesn't
work well together with Gson.
Therefore it uses a well-behaving POJO
helper class (with properties
allowsChildren
, userObject
and children
) and lets Gson
deserialize the JSON content into this class.
Then the POJO
object (and its POJO
children) is converted to a
DefaultMutableTreeNode
object (with DefaultMutableTreeNode
children).
public class DefaultMutableTreeNodeDeserializer implements JsonDeserializer<DefaultMutableTreeNode> {
@Override
public DefaultMutableTreeNode deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
return context.<POJO>deserialize(json, POJO.class).toDefaultMutableTreeNode();
}
private static class POJO {
private boolean allowsChildren;
private Object userObject;
private List<POJO> children;
// no need for: POJO parent
public DefaultMutableTreeNode toDefaultMutableTreeNode() {
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
node.setAllowsChildren(allowsChildren);
node.setUserObject(userObject);
if (children != null) {
for (POJO child : children) {
node.add(child.toDefaultMutableTreeNode()); // recursion!
// this did also set the parent of the child-node
}
}
return node;
}
// Following setters needed by Gson's deserialization:
public void setAllowsChildren(boolean allowsChildren) {
this.allowsChildren = allowsChildren;
}
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
public void setChildren(List<POJO> children) {
this.children = children;
}
}
}
add a comment |
This is an improved alternative to my older answer which used implementations of JsonSerializer
and JsonDeserializer
for DefaultMutableTreeNode
.
The API doc of these 2 interfaces says:
New applications should prefer
TypeAdapter
, whose streaming API
is more efficient than this interface's tree API.
Let's therefore use this preferred approach and implement a
TypeAdapter
for DefaultMutableTreeNode
.
For using it you create your Gson
instance like this
(instead of just using new Gson()
):
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(DefaultMutableTreeNodeTypeAdapter.FACTORY)
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeTypeAdapter
below is responsible for
converting a DefaultMutableTreeNode
to and from JSON.
It writes/reads its properties allowsChildren
, userObject
and children
.
There is no need to write the parent
property, because the parent-child
relations are already encoded in the nested structure of the JSON-output.
public class DefaultMutableTreeNodeTypeAdapter extends TypeAdapter<DefaultMutableTreeNode> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == DefaultMutableTreeNode.class) {
return (TypeAdapter<T>) new DefaultMutableTreeNodeTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private DefaultMutableTreeNodeTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, DefaultMutableTreeNode node) throws IOException {
out.beginObject();
out.name("allowsChildren");
out.value(node.getAllowsChildren());
out.name("userObject");
gson.toJson(node.getUserObject(), Object.class, out);
if (node.getChildCount() > 0) {
out.name("children");
out.beginArray();
for (Object childNode : Collections.list(node.children())) {
gson.toJson(childNode, Object.class, out); // recursion!
}
out.endArray();
}
// No need to write node.getParent(), it would lead to infinite recursion.
out.endObject();
}
@Override
public DefaultMutableTreeNode read(JsonReader in) throws IOException {
in.beginObject();
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
while (in.hasNext()) {
switch (in.nextName()) {
case "allowsChildren":
node.setAllowsChildren(in.nextBoolean());
break;
case "userObject":
node.setUserObject(gson.fromJson(in, Object.class));
break;
case "children":
in.beginArray();
while (in.hasNext()) {
node.add(read(in)); // recursion!
// this did also set the parent of the child-node
}
in.endArray();
break;
default:
in.skipValue();
break;
}
}
in.endObject();
return node;
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53997112%2fhow-to-serialize-defaultmutabletreenode-java-to-json%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Swing's DefaultMutableTreeNode
class is a tree-like data structure
which contains instances of this same type both as children
and as parent
.
That's why Gson's default serializer ran into infinite recursion
and hence threw a StackOverflowError
.
To solve this problem you need to customize your Gson
with a smarter JsonSerializer
specially crafted for converting a DefaultMutableTreeNode
to JSON.
As a bonus you might also want to provide a JsonDeserializer
for converting such JSON back to a DefaultMutableTreeNode
.
For that create your Gson
instance not just by new Gson()
, but by
Gson gson = new GsonBuilder()
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeSerializer())
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeDeserializer())
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeSerializer
below is responsible
for converting a DefaultMutableTreeNode
to JSON.
It converts its properties allowsChildren
, userObject
and children
to JSON.
Note that it does not convert the parent
property to JSON,
because doing that would produce an inifinite recursion again.
public class DefaultMutableTreeNodeSerializer implements JsonSerializer<DefaultMutableTreeNode> {
@Override
public JsonElement serialize(DefaultMutableTreeNode src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("allowsChildren", src.getAllowsChildren());
jsonObject.add("userObject", context.serialize(src.getUserObject()));
if (src.getChildCount() > 0) {
jsonObject.add("children", context.serialize(Collections.list(src.children())));
}
return jsonObject;
}
}
For testing let us serialize the root node of a sample JTree
to JSON,
and then deserialize it again.
JTree tree = new JTree(); // create a sample tree
Object topNode = tree.getModel().getRoot(); // a DefaultMutableTreeNode
String jsonString = gson.toJson(topNode);
System.out.println(jsonString);
DefaultMutableTreeNode topNode2 = gson.fromJson(jsonString, DefaultMutableTreeNode.class);
It generates the following JSON output:
{
"allowsChildren": true,
"userObject": "JTree",
"children": [
{
"allowsChildren": true,
"userObject": "colors",
"children": [
{
"allowsChildren": true,
"userObject": "blue"
},
{
"allowsChildren": true,
"userObject": "violet"
},
{
"allowsChildren": true,
"userObject": "red"
},
{
"allowsChildren": true,
"userObject": "yellow"
}
]
},
{
"allowsChildren": true,
"userObject": "sports",
"children": [
{
"allowsChildren": true,
"userObject": "basketball"
},
{
"allowsChildren": true,
"userObject": "soccer"
},
{
"allowsChildren": true,
"userObject": "football"
},
{
"allowsChildren": true,
"userObject": "hockey"
}
]
},
{
"allowsChildren": true,
"userObject": "food",
"children": [
{
"allowsChildren": true,
"userObject": "hot dogs"
},
{
"allowsChildren": true,
"userObject": "pizza"
},
{
"allowsChildren": true,
"userObject": "ravioli"
},
{
"allowsChildren": true,
"userObject": "bananas"
}
]
}
]
}
The DefaultMutableTreeNodeDeserializer
below is responsible
for converting JSON back to a DefaultMutableTreeNode
.
It uses the same idea as the deserializer from
How to serialize/deserialize a DefaultMutableTreeNode with Jackson?.
The DefaultMutableTreeNode
is not very POJO-like and thus doesn't
work well together with Gson.
Therefore it uses a well-behaving POJO
helper class (with properties
allowsChildren
, userObject
and children
) and lets Gson
deserialize the JSON content into this class.
Then the POJO
object (and its POJO
children) is converted to a
DefaultMutableTreeNode
object (with DefaultMutableTreeNode
children).
public class DefaultMutableTreeNodeDeserializer implements JsonDeserializer<DefaultMutableTreeNode> {
@Override
public DefaultMutableTreeNode deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
return context.<POJO>deserialize(json, POJO.class).toDefaultMutableTreeNode();
}
private static class POJO {
private boolean allowsChildren;
private Object userObject;
private List<POJO> children;
// no need for: POJO parent
public DefaultMutableTreeNode toDefaultMutableTreeNode() {
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
node.setAllowsChildren(allowsChildren);
node.setUserObject(userObject);
if (children != null) {
for (POJO child : children) {
node.add(child.toDefaultMutableTreeNode()); // recursion!
// this did also set the parent of the child-node
}
}
return node;
}
// Following setters needed by Gson's deserialization:
public void setAllowsChildren(boolean allowsChildren) {
this.allowsChildren = allowsChildren;
}
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
public void setChildren(List<POJO> children) {
this.children = children;
}
}
}
add a comment |
Swing's DefaultMutableTreeNode
class is a tree-like data structure
which contains instances of this same type both as children
and as parent
.
That's why Gson's default serializer ran into infinite recursion
and hence threw a StackOverflowError
.
To solve this problem you need to customize your Gson
with a smarter JsonSerializer
specially crafted for converting a DefaultMutableTreeNode
to JSON.
As a bonus you might also want to provide a JsonDeserializer
for converting such JSON back to a DefaultMutableTreeNode
.
For that create your Gson
instance not just by new Gson()
, but by
Gson gson = new GsonBuilder()
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeSerializer())
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeDeserializer())
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeSerializer
below is responsible
for converting a DefaultMutableTreeNode
to JSON.
It converts its properties allowsChildren
, userObject
and children
to JSON.
Note that it does not convert the parent
property to JSON,
because doing that would produce an inifinite recursion again.
public class DefaultMutableTreeNodeSerializer implements JsonSerializer<DefaultMutableTreeNode> {
@Override
public JsonElement serialize(DefaultMutableTreeNode src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("allowsChildren", src.getAllowsChildren());
jsonObject.add("userObject", context.serialize(src.getUserObject()));
if (src.getChildCount() > 0) {
jsonObject.add("children", context.serialize(Collections.list(src.children())));
}
return jsonObject;
}
}
For testing let us serialize the root node of a sample JTree
to JSON,
and then deserialize it again.
JTree tree = new JTree(); // create a sample tree
Object topNode = tree.getModel().getRoot(); // a DefaultMutableTreeNode
String jsonString = gson.toJson(topNode);
System.out.println(jsonString);
DefaultMutableTreeNode topNode2 = gson.fromJson(jsonString, DefaultMutableTreeNode.class);
It generates the following JSON output:
{
"allowsChildren": true,
"userObject": "JTree",
"children": [
{
"allowsChildren": true,
"userObject": "colors",
"children": [
{
"allowsChildren": true,
"userObject": "blue"
},
{
"allowsChildren": true,
"userObject": "violet"
},
{
"allowsChildren": true,
"userObject": "red"
},
{
"allowsChildren": true,
"userObject": "yellow"
}
]
},
{
"allowsChildren": true,
"userObject": "sports",
"children": [
{
"allowsChildren": true,
"userObject": "basketball"
},
{
"allowsChildren": true,
"userObject": "soccer"
},
{
"allowsChildren": true,
"userObject": "football"
},
{
"allowsChildren": true,
"userObject": "hockey"
}
]
},
{
"allowsChildren": true,
"userObject": "food",
"children": [
{
"allowsChildren": true,
"userObject": "hot dogs"
},
{
"allowsChildren": true,
"userObject": "pizza"
},
{
"allowsChildren": true,
"userObject": "ravioli"
},
{
"allowsChildren": true,
"userObject": "bananas"
}
]
}
]
}
The DefaultMutableTreeNodeDeserializer
below is responsible
for converting JSON back to a DefaultMutableTreeNode
.
It uses the same idea as the deserializer from
How to serialize/deserialize a DefaultMutableTreeNode with Jackson?.
The DefaultMutableTreeNode
is not very POJO-like and thus doesn't
work well together with Gson.
Therefore it uses a well-behaving POJO
helper class (with properties
allowsChildren
, userObject
and children
) and lets Gson
deserialize the JSON content into this class.
Then the POJO
object (and its POJO
children) is converted to a
DefaultMutableTreeNode
object (with DefaultMutableTreeNode
children).
public class DefaultMutableTreeNodeDeserializer implements JsonDeserializer<DefaultMutableTreeNode> {
@Override
public DefaultMutableTreeNode deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
return context.<POJO>deserialize(json, POJO.class).toDefaultMutableTreeNode();
}
private static class POJO {
private boolean allowsChildren;
private Object userObject;
private List<POJO> children;
// no need for: POJO parent
public DefaultMutableTreeNode toDefaultMutableTreeNode() {
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
node.setAllowsChildren(allowsChildren);
node.setUserObject(userObject);
if (children != null) {
for (POJO child : children) {
node.add(child.toDefaultMutableTreeNode()); // recursion!
// this did also set the parent of the child-node
}
}
return node;
}
// Following setters needed by Gson's deserialization:
public void setAllowsChildren(boolean allowsChildren) {
this.allowsChildren = allowsChildren;
}
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
public void setChildren(List<POJO> children) {
this.children = children;
}
}
}
add a comment |
Swing's DefaultMutableTreeNode
class is a tree-like data structure
which contains instances of this same type both as children
and as parent
.
That's why Gson's default serializer ran into infinite recursion
and hence threw a StackOverflowError
.
To solve this problem you need to customize your Gson
with a smarter JsonSerializer
specially crafted for converting a DefaultMutableTreeNode
to JSON.
As a bonus you might also want to provide a JsonDeserializer
for converting such JSON back to a DefaultMutableTreeNode
.
For that create your Gson
instance not just by new Gson()
, but by
Gson gson = new GsonBuilder()
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeSerializer())
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeDeserializer())
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeSerializer
below is responsible
for converting a DefaultMutableTreeNode
to JSON.
It converts its properties allowsChildren
, userObject
and children
to JSON.
Note that it does not convert the parent
property to JSON,
because doing that would produce an inifinite recursion again.
public class DefaultMutableTreeNodeSerializer implements JsonSerializer<DefaultMutableTreeNode> {
@Override
public JsonElement serialize(DefaultMutableTreeNode src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("allowsChildren", src.getAllowsChildren());
jsonObject.add("userObject", context.serialize(src.getUserObject()));
if (src.getChildCount() > 0) {
jsonObject.add("children", context.serialize(Collections.list(src.children())));
}
return jsonObject;
}
}
For testing let us serialize the root node of a sample JTree
to JSON,
and then deserialize it again.
JTree tree = new JTree(); // create a sample tree
Object topNode = tree.getModel().getRoot(); // a DefaultMutableTreeNode
String jsonString = gson.toJson(topNode);
System.out.println(jsonString);
DefaultMutableTreeNode topNode2 = gson.fromJson(jsonString, DefaultMutableTreeNode.class);
It generates the following JSON output:
{
"allowsChildren": true,
"userObject": "JTree",
"children": [
{
"allowsChildren": true,
"userObject": "colors",
"children": [
{
"allowsChildren": true,
"userObject": "blue"
},
{
"allowsChildren": true,
"userObject": "violet"
},
{
"allowsChildren": true,
"userObject": "red"
},
{
"allowsChildren": true,
"userObject": "yellow"
}
]
},
{
"allowsChildren": true,
"userObject": "sports",
"children": [
{
"allowsChildren": true,
"userObject": "basketball"
},
{
"allowsChildren": true,
"userObject": "soccer"
},
{
"allowsChildren": true,
"userObject": "football"
},
{
"allowsChildren": true,
"userObject": "hockey"
}
]
},
{
"allowsChildren": true,
"userObject": "food",
"children": [
{
"allowsChildren": true,
"userObject": "hot dogs"
},
{
"allowsChildren": true,
"userObject": "pizza"
},
{
"allowsChildren": true,
"userObject": "ravioli"
},
{
"allowsChildren": true,
"userObject": "bananas"
}
]
}
]
}
The DefaultMutableTreeNodeDeserializer
below is responsible
for converting JSON back to a DefaultMutableTreeNode
.
It uses the same idea as the deserializer from
How to serialize/deserialize a DefaultMutableTreeNode with Jackson?.
The DefaultMutableTreeNode
is not very POJO-like and thus doesn't
work well together with Gson.
Therefore it uses a well-behaving POJO
helper class (with properties
allowsChildren
, userObject
and children
) and lets Gson
deserialize the JSON content into this class.
Then the POJO
object (and its POJO
children) is converted to a
DefaultMutableTreeNode
object (with DefaultMutableTreeNode
children).
public class DefaultMutableTreeNodeDeserializer implements JsonDeserializer<DefaultMutableTreeNode> {
@Override
public DefaultMutableTreeNode deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
return context.<POJO>deserialize(json, POJO.class).toDefaultMutableTreeNode();
}
private static class POJO {
private boolean allowsChildren;
private Object userObject;
private List<POJO> children;
// no need for: POJO parent
public DefaultMutableTreeNode toDefaultMutableTreeNode() {
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
node.setAllowsChildren(allowsChildren);
node.setUserObject(userObject);
if (children != null) {
for (POJO child : children) {
node.add(child.toDefaultMutableTreeNode()); // recursion!
// this did also set the parent of the child-node
}
}
return node;
}
// Following setters needed by Gson's deserialization:
public void setAllowsChildren(boolean allowsChildren) {
this.allowsChildren = allowsChildren;
}
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
public void setChildren(List<POJO> children) {
this.children = children;
}
}
}
Swing's DefaultMutableTreeNode
class is a tree-like data structure
which contains instances of this same type both as children
and as parent
.
That's why Gson's default serializer ran into infinite recursion
and hence threw a StackOverflowError
.
To solve this problem you need to customize your Gson
with a smarter JsonSerializer
specially crafted for converting a DefaultMutableTreeNode
to JSON.
As a bonus you might also want to provide a JsonDeserializer
for converting such JSON back to a DefaultMutableTreeNode
.
For that create your Gson
instance not just by new Gson()
, but by
Gson gson = new GsonBuilder()
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeSerializer())
.registerTypeAdapter(DefaultMutableTreeNode.class, new DefaultMutableTreeNodeDeserializer())
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeSerializer
below is responsible
for converting a DefaultMutableTreeNode
to JSON.
It converts its properties allowsChildren
, userObject
and children
to JSON.
Note that it does not convert the parent
property to JSON,
because doing that would produce an inifinite recursion again.
public class DefaultMutableTreeNodeSerializer implements JsonSerializer<DefaultMutableTreeNode> {
@Override
public JsonElement serialize(DefaultMutableTreeNode src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("allowsChildren", src.getAllowsChildren());
jsonObject.add("userObject", context.serialize(src.getUserObject()));
if (src.getChildCount() > 0) {
jsonObject.add("children", context.serialize(Collections.list(src.children())));
}
return jsonObject;
}
}
For testing let us serialize the root node of a sample JTree
to JSON,
and then deserialize it again.
JTree tree = new JTree(); // create a sample tree
Object topNode = tree.getModel().getRoot(); // a DefaultMutableTreeNode
String jsonString = gson.toJson(topNode);
System.out.println(jsonString);
DefaultMutableTreeNode topNode2 = gson.fromJson(jsonString, DefaultMutableTreeNode.class);
It generates the following JSON output:
{
"allowsChildren": true,
"userObject": "JTree",
"children": [
{
"allowsChildren": true,
"userObject": "colors",
"children": [
{
"allowsChildren": true,
"userObject": "blue"
},
{
"allowsChildren": true,
"userObject": "violet"
},
{
"allowsChildren": true,
"userObject": "red"
},
{
"allowsChildren": true,
"userObject": "yellow"
}
]
},
{
"allowsChildren": true,
"userObject": "sports",
"children": [
{
"allowsChildren": true,
"userObject": "basketball"
},
{
"allowsChildren": true,
"userObject": "soccer"
},
{
"allowsChildren": true,
"userObject": "football"
},
{
"allowsChildren": true,
"userObject": "hockey"
}
]
},
{
"allowsChildren": true,
"userObject": "food",
"children": [
{
"allowsChildren": true,
"userObject": "hot dogs"
},
{
"allowsChildren": true,
"userObject": "pizza"
},
{
"allowsChildren": true,
"userObject": "ravioli"
},
{
"allowsChildren": true,
"userObject": "bananas"
}
]
}
]
}
The DefaultMutableTreeNodeDeserializer
below is responsible
for converting JSON back to a DefaultMutableTreeNode
.
It uses the same idea as the deserializer from
How to serialize/deserialize a DefaultMutableTreeNode with Jackson?.
The DefaultMutableTreeNode
is not very POJO-like and thus doesn't
work well together with Gson.
Therefore it uses a well-behaving POJO
helper class (with properties
allowsChildren
, userObject
and children
) and lets Gson
deserialize the JSON content into this class.
Then the POJO
object (and its POJO
children) is converted to a
DefaultMutableTreeNode
object (with DefaultMutableTreeNode
children).
public class DefaultMutableTreeNodeDeserializer implements JsonDeserializer<DefaultMutableTreeNode> {
@Override
public DefaultMutableTreeNode deserialize(JsonElement json, Type type, JsonDeserializationContext context) {
return context.<POJO>deserialize(json, POJO.class).toDefaultMutableTreeNode();
}
private static class POJO {
private boolean allowsChildren;
private Object userObject;
private List<POJO> children;
// no need for: POJO parent
public DefaultMutableTreeNode toDefaultMutableTreeNode() {
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
node.setAllowsChildren(allowsChildren);
node.setUserObject(userObject);
if (children != null) {
for (POJO child : children) {
node.add(child.toDefaultMutableTreeNode()); // recursion!
// this did also set the parent of the child-node
}
}
return node;
}
// Following setters needed by Gson's deserialization:
public void setAllowsChildren(boolean allowsChildren) {
this.allowsChildren = allowsChildren;
}
public void setUserObject(Object userObject) {
this.userObject = userObject;
}
public void setChildren(List<POJO> children) {
this.children = children;
}
}
}
edited Mar 8 at 18:37
answered Jan 2 at 0:50
Thomas FritschThomas Fritsch
5,436122135
5,436122135
add a comment |
add a comment |
This is an improved alternative to my older answer which used implementations of JsonSerializer
and JsonDeserializer
for DefaultMutableTreeNode
.
The API doc of these 2 interfaces says:
New applications should prefer
TypeAdapter
, whose streaming API
is more efficient than this interface's tree API.
Let's therefore use this preferred approach and implement a
TypeAdapter
for DefaultMutableTreeNode
.
For using it you create your Gson
instance like this
(instead of just using new Gson()
):
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(DefaultMutableTreeNodeTypeAdapter.FACTORY)
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeTypeAdapter
below is responsible for
converting a DefaultMutableTreeNode
to and from JSON.
It writes/reads its properties allowsChildren
, userObject
and children
.
There is no need to write the parent
property, because the parent-child
relations are already encoded in the nested structure of the JSON-output.
public class DefaultMutableTreeNodeTypeAdapter extends TypeAdapter<DefaultMutableTreeNode> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == DefaultMutableTreeNode.class) {
return (TypeAdapter<T>) new DefaultMutableTreeNodeTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private DefaultMutableTreeNodeTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, DefaultMutableTreeNode node) throws IOException {
out.beginObject();
out.name("allowsChildren");
out.value(node.getAllowsChildren());
out.name("userObject");
gson.toJson(node.getUserObject(), Object.class, out);
if (node.getChildCount() > 0) {
out.name("children");
out.beginArray();
for (Object childNode : Collections.list(node.children())) {
gson.toJson(childNode, Object.class, out); // recursion!
}
out.endArray();
}
// No need to write node.getParent(), it would lead to infinite recursion.
out.endObject();
}
@Override
public DefaultMutableTreeNode read(JsonReader in) throws IOException {
in.beginObject();
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
while (in.hasNext()) {
switch (in.nextName()) {
case "allowsChildren":
node.setAllowsChildren(in.nextBoolean());
break;
case "userObject":
node.setUserObject(gson.fromJson(in, Object.class));
break;
case "children":
in.beginArray();
while (in.hasNext()) {
node.add(read(in)); // recursion!
// this did also set the parent of the child-node
}
in.endArray();
break;
default:
in.skipValue();
break;
}
}
in.endObject();
return node;
}
}
add a comment |
This is an improved alternative to my older answer which used implementations of JsonSerializer
and JsonDeserializer
for DefaultMutableTreeNode
.
The API doc of these 2 interfaces says:
New applications should prefer
TypeAdapter
, whose streaming API
is more efficient than this interface's tree API.
Let's therefore use this preferred approach and implement a
TypeAdapter
for DefaultMutableTreeNode
.
For using it you create your Gson
instance like this
(instead of just using new Gson()
):
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(DefaultMutableTreeNodeTypeAdapter.FACTORY)
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeTypeAdapter
below is responsible for
converting a DefaultMutableTreeNode
to and from JSON.
It writes/reads its properties allowsChildren
, userObject
and children
.
There is no need to write the parent
property, because the parent-child
relations are already encoded in the nested structure of the JSON-output.
public class DefaultMutableTreeNodeTypeAdapter extends TypeAdapter<DefaultMutableTreeNode> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == DefaultMutableTreeNode.class) {
return (TypeAdapter<T>) new DefaultMutableTreeNodeTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private DefaultMutableTreeNodeTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, DefaultMutableTreeNode node) throws IOException {
out.beginObject();
out.name("allowsChildren");
out.value(node.getAllowsChildren());
out.name("userObject");
gson.toJson(node.getUserObject(), Object.class, out);
if (node.getChildCount() > 0) {
out.name("children");
out.beginArray();
for (Object childNode : Collections.list(node.children())) {
gson.toJson(childNode, Object.class, out); // recursion!
}
out.endArray();
}
// No need to write node.getParent(), it would lead to infinite recursion.
out.endObject();
}
@Override
public DefaultMutableTreeNode read(JsonReader in) throws IOException {
in.beginObject();
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
while (in.hasNext()) {
switch (in.nextName()) {
case "allowsChildren":
node.setAllowsChildren(in.nextBoolean());
break;
case "userObject":
node.setUserObject(gson.fromJson(in, Object.class));
break;
case "children":
in.beginArray();
while (in.hasNext()) {
node.add(read(in)); // recursion!
// this did also set the parent of the child-node
}
in.endArray();
break;
default:
in.skipValue();
break;
}
}
in.endObject();
return node;
}
}
add a comment |
This is an improved alternative to my older answer which used implementations of JsonSerializer
and JsonDeserializer
for DefaultMutableTreeNode
.
The API doc of these 2 interfaces says:
New applications should prefer
TypeAdapter
, whose streaming API
is more efficient than this interface's tree API.
Let's therefore use this preferred approach and implement a
TypeAdapter
for DefaultMutableTreeNode
.
For using it you create your Gson
instance like this
(instead of just using new Gson()
):
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(DefaultMutableTreeNodeTypeAdapter.FACTORY)
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeTypeAdapter
below is responsible for
converting a DefaultMutableTreeNode
to and from JSON.
It writes/reads its properties allowsChildren
, userObject
and children
.
There is no need to write the parent
property, because the parent-child
relations are already encoded in the nested structure of the JSON-output.
public class DefaultMutableTreeNodeTypeAdapter extends TypeAdapter<DefaultMutableTreeNode> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == DefaultMutableTreeNode.class) {
return (TypeAdapter<T>) new DefaultMutableTreeNodeTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private DefaultMutableTreeNodeTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, DefaultMutableTreeNode node) throws IOException {
out.beginObject();
out.name("allowsChildren");
out.value(node.getAllowsChildren());
out.name("userObject");
gson.toJson(node.getUserObject(), Object.class, out);
if (node.getChildCount() > 0) {
out.name("children");
out.beginArray();
for (Object childNode : Collections.list(node.children())) {
gson.toJson(childNode, Object.class, out); // recursion!
}
out.endArray();
}
// No need to write node.getParent(), it would lead to infinite recursion.
out.endObject();
}
@Override
public DefaultMutableTreeNode read(JsonReader in) throws IOException {
in.beginObject();
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
while (in.hasNext()) {
switch (in.nextName()) {
case "allowsChildren":
node.setAllowsChildren(in.nextBoolean());
break;
case "userObject":
node.setUserObject(gson.fromJson(in, Object.class));
break;
case "children":
in.beginArray();
while (in.hasNext()) {
node.add(read(in)); // recursion!
// this did also set the parent of the child-node
}
in.endArray();
break;
default:
in.skipValue();
break;
}
}
in.endObject();
return node;
}
}
This is an improved alternative to my older answer which used implementations of JsonSerializer
and JsonDeserializer
for DefaultMutableTreeNode
.
The API doc of these 2 interfaces says:
New applications should prefer
TypeAdapter
, whose streaming API
is more efficient than this interface's tree API.
Let's therefore use this preferred approach and implement a
TypeAdapter
for DefaultMutableTreeNode
.
For using it you create your Gson
instance like this
(instead of just using new Gson()
):
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(DefaultMutableTreeNodeTypeAdapter.FACTORY)
.setPrettyPrinting()
.create();
The DefaultMutableTreeNodeTypeAdapter
below is responsible for
converting a DefaultMutableTreeNode
to and from JSON.
It writes/reads its properties allowsChildren
, userObject
and children
.
There is no need to write the parent
property, because the parent-child
relations are already encoded in the nested structure of the JSON-output.
public class DefaultMutableTreeNodeTypeAdapter extends TypeAdapter<DefaultMutableTreeNode> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() == DefaultMutableTreeNode.class) {
return (TypeAdapter<T>) new DefaultMutableTreeNodeTypeAdapter(gson);
}
return null;
}
};
private final Gson gson;
private DefaultMutableTreeNodeTypeAdapter(Gson gson) {
this.gson = gson;
}
@Override
public void write(JsonWriter out, DefaultMutableTreeNode node) throws IOException {
out.beginObject();
out.name("allowsChildren");
out.value(node.getAllowsChildren());
out.name("userObject");
gson.toJson(node.getUserObject(), Object.class, out);
if (node.getChildCount() > 0) {
out.name("children");
out.beginArray();
for (Object childNode : Collections.list(node.children())) {
gson.toJson(childNode, Object.class, out); // recursion!
}
out.endArray();
}
// No need to write node.getParent(), it would lead to infinite recursion.
out.endObject();
}
@Override
public DefaultMutableTreeNode read(JsonReader in) throws IOException {
in.beginObject();
DefaultMutableTreeNode node = new DefaultMutableTreeNode();
while (in.hasNext()) {
switch (in.nextName()) {
case "allowsChildren":
node.setAllowsChildren(in.nextBoolean());
break;
case "userObject":
node.setUserObject(gson.fromJson(in, Object.class));
break;
case "children":
in.beginArray();
while (in.hasNext()) {
node.add(read(in)); // recursion!
// this did also set the parent of the child-node
}
in.endArray();
break;
default:
in.skipValue();
break;
}
}
in.endObject();
return node;
}
}
answered 8 hours ago
Thomas FritschThomas Fritsch
5,436122135
5,436122135
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53997112%2fhow-to-serialize-defaultmutabletreenode-java-to-json%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown