简体   繁体   中英

Gson - Simplify serialization on some fields

I am trying to serialize complex objects in a tree structure. Example:

class Tree{
    private Node1 rootNode;
    private ArrayList<Node> allNodes;
}

class Node1 extends Node{
    private String id;
    private Node2 node2;
}

class Node2 extends Node{
    private String id;
    ...
}

The field rootNode should possibly only contain the rootNodeId in the JSON. Still, I need the actual Node1 object there in my code. The same would be nice for the Node2 property in Node1. The idea is to only save a reference there, since the full objects will be in the allNodes array. The JSON should possibly look like this:

{Tree: {rootNodeId: 1, allNodes: [{id: 1, node2Id: 2}, {id: 2}]}}

How can I achieve this? Thanks for any idea!

There is the Data Transfer Object pattern that is very often used in serialization and deserialization. It also has a lot of advantages, like decoupling various forms of the same logical object at different application layers, precise configuration for a particular serialization library (what if someday you would like to switch to Jackson?), etc.

Assuming your value objects (opposite of DTO) have constructors, let's build the object model first (I'm using Java 6 here and later not to confuse you):

private static Tree createTree() {
    final Tree tree = new Tree();
    tree.rootNode = new Node1("1");
    tree.allNodes = new ArrayList<Node>();
    tree.allNodes.add(new Node1("1", new Node2("2")));
    tree.allNodes.add(new Node2("2"));
    return tree;
}

Then, just define your DTO classes in order to align with your requirements to GSON:

private static final class RootDto {

    @SerializedName("Tree")
    @SuppressWarnings("unused")
    private final TreeDto tree;

    private RootDto(final TreeDto tree) {
        this.tree = tree;
    }

}

private static final class TreeDto {

    @SerializedName("rootNodeId")
    @SuppressWarnings("unused")
    private final String rootNodeId;

    @SerializedName("allNodes")
    @SuppressWarnings("unused")
    private final List<NodeDto> allNodes;

    private TreeDto(final String rootNodeId, final List<NodeDto> allNodes) {
        this.rootNodeId = rootNodeId;
        this.allNodes = allNodes;
    }

}

private abstract static class NodeDto {
}

private static final class Node1Dto
        extends NodeDto {

    @SerializedName("id")
    @SuppressWarnings("unused")
    private final String id;

    @SerializedName("node2Id")
    @SuppressWarnings("unused")
    private final String node2Id;

    private Node1Dto(final String id, final String node2Id) {
        this.id = id;
        this.node2Id = node2Id;
    }

}

private static final class Node2Dto
        extends NodeDto {

    @SerializedName("id")
    @SuppressWarnings("unused")
    private final String id;

    private Node2Dto(final String id) {
        this.id = id;
    }

}

Note that I'm using static final classes (declared within one outer class) here just in order to simplify the demo. Also I'm using @SerializedName to explicitly define the names of the JSON fields (it allows to rename the fields in Java not touching the JSON fields), and @SuppressWarnings in order to suppress warnings for fields that are known to the compiler as unused since they are used in the result JSON anyway. Another option of custom DTOs is using nested java.util.Map s but I do believe that class-based DTOs gives you more type safety. Now just convert the value object to its data-transfer representation:

private static RootDto toDto(final Tree tree) {
    final List<NodeDto> allNodes = new ArrayList<>();
    for ( final Node node : tree.allNodes ) {
        final NodeDto nodeDto;
        if ( node instanceof Node1 ) {
            final Node1 node1 = (Node1) node;
            nodeDto = new Node1Dto(node1.id, node1.node2.id);
        } else if ( node instanceof Node2 ) {
            final Node2 node2 = (Node2) node;
            nodeDto = new Node2Dto(node2.id);
        } else {
            throw new AssertionError("must never happen: " + node);
        }
        allNodes.add(nodeDto);
    }
    final TreeDto treeDto = new TreeDto(tree.rootNode.id, allNodes);
    return new RootDto(treeDto);
}

And build it all together:

final Gson gson = new Gson();
final Tree tree = createTree();
out.println(gson.toJson(tree));
out.println(gson.toJson(toDto(tree)));

The output is as follows:

{"rootNode":{"id":"1"},"allNodes":[{"id":"1","node2":{"id":"2"}},{"id":"2"}]}
{"Tree":{"rootNodeId":"1","allNodes":[{"id":"1","node2Id":"2"},{"id":"2"}]}}

GSON also supports custom serializers that would allow you to solve the problem in a more low-level fashioned way, but I would say that it's definitely an overkill here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM