Say I have a parametrized tree implemented in Java as follows:
public class Tree<E> { private static class Node { E element; List<Node> children. } Node root; //... You get the idea. }
The idea here is that the implementation above is only concerned with the topology of the tree, but does not know anything about the elements that will be stored in the tree by an instantiation.
Now, say I want my tree elements to be geographies. The reason they are organized in trees is because continents contain countries, countries contain states or a provinces, and so on. For simplicity, a geography has a name and a type:
public class GeoElement { String name; String type; }
So that, finally, the geo hierarchy looks like so:
public class Geography extends Tree<GeoElement> {}
Now to Jackson serialization. Assuming the Jackson serializer can see the fields, the direct serialization of this implementation will look like this:
{ "root": { "element": { "name":"Latin America", "type":"Continent" } "children": [ { "element": { "name":"Brazil", "type":"Country" }, "children": [ // ... A list of states in Brazil ] }, { "element": { "name":"Argentina", "type":"Country" }, "children": [ // ... A list of states in Argentina ] } ] }
This JSON rendering is no good because it contains the unnecessary artifacts from the Tree and Node classes, i.e. "root" and "element". What I need instead is this:
{ "name":"Latin America", "type":"Continent" "children": [ { "name":"Brazil", "type":"Country" "children": [ // ... A list of states in Brazil ] }, { "name":"Argentina", "type":"Country" "children": [ // ... A list of states in Argentina ] } ] }
Any help is most appreciated. -Igor.
4 Answers
Answers 1
What you need is @JsonUnwrapped
.
Annotation used to indicate that a property should be serialized "unwrapped"; that is, if it would be serialized as JSON Object, its properties are instead included as properties of its containing Object
Add this annotation to the root
field of Tree
& element
field of Node
classes as follows:
public class Tree<E> { private static class Node { @JsonUnwrapped E element; List<Node> children. } @JsonUnwrapped Node root; //... You get the idea. }
And it will give you your desired output:
{ "name": "Latin America", "type": "Continent", "children": [{ "name": "Brazil", "type": "Country", "children": [] }, { "name": "Argentina", "type": "Country", "children": [] }] }
Answers 2
Perhaps use @JsonValue
like so:
public class Tree<E> { @JsonValue Node root; }
if all you need is to just "unwrap" your tree?
Answers 3
Your best bet will be to build and register a custom serializer for your objects.
Define your serializer:
public class NodeSerializer extends StdSerializer<Node> {
Then on your Node class:
@JsonSerialize(using = NodeSerializer.class) public class Node { }
And inside of the NodeSerializer
@Override public void serialize( Node node, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeStartObject(); jgen.writeStringField("name", node.element.name); jgen.writeStringField("type", node.element.type); //Process children serializeFields(node, jgen, provider); jgen.writeEndObject(); }
This general framework will let you control how the elements get serialized. You may need to @JsonIgnore
the element
object inside of the Node
as well since your custom serializer is taking care of pushing that info into the resulting JSON. There is a lot online about custom serializers and overriding default JSON export.
You can get rid of root in a similar way with a serializer for the Tree implementation.
If you don't want to register the serializer on the class you can also do it on a one at a time basis using the ObjectMapper
:
ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addSerializer(Node.class, new NodeSerializer()); mapper.registerModule(module); String serialized = mapper.writeValueAsString(tree);
The annotation approach will apply globally. This approach allows some control of how/where your custom serializer is used.
Answers 4
For removing the element
type, one possibility would be to change your structure so that the name and the type will be directly included in each node:
public class TreeGeo { private static class Node { String name; String type; List<Node> children. } Node root; }
For removing the root
type, I don't know. I suppose that you could extract a sub-object from the jsonObject but I don't know much about Jackson. However, you could give it a better name like world
or manipulate the resulting string to remove it manually with some string manipulations.
0 comments:
Post a Comment