Custom Jackson serializer for a generic tree - java

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.

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": []
}]
}

Perhaps use #JsonValue like so:
public class Tree<E> {
#JsonValue
Node root;
}
if all you need is to just "unwrap" your tree?

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.

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.

Related

Jackson deserialization of union object with sibling type

I'm trying to combine a few features of Jackson such that I can deserialize a {type,value} pair in json into a Union type in java representing all the information but can't work out how to do it. Help would be greatly appreciated.
Here is what I'm working with:
The java Union type enforces that only a single value can be set at any one time, think of this like an enum but with dynamic data.
The value of the union can take any number of types, some scalar and some object or collection.
Multiple options for the union can share the same type, i.e. noyes and offon are both boolean types in the example below.
I'm not in control of the json structure in any way (it's the data passed to/from an external API) so can't change it at all.
The java classes are generated code from Thrift idl so I can't add annotations to them or adjust their structure drastically. I am in control of the idl though, but would like to keep it fairly clean and free of leaky patterns like the {type, value} that is needed for json but not strongly typed languages.
The type names used in the json (and some of the field names) conflict with java (and other language) keywords which is why each value is suffixed: noyes -> noYesValue, float -> floatValue
A concrete example
I have a json document that looks like this:
{
"obj": [
{
"type": "noyes",
"value": true,
"id": 1
}, {
"type": "offon",
"value": false,
"id": 2
}, {
"type": "text",
"value": "hello",
"id": 3
}, {
"type": "float",
"value": 1.2,
"id": 4
}, {
"type": "times",
"value": [{"s": "12:22", "e": "16:00"}]
"id": 5
}
]
}
And java classes that look like this:
class Response {
List<Item> obj;
}
class Item {
int id;
Value value;
}
class Value {
enum Fields { NO_YES_VALUE, OFF_ON_VALUE, TEXT_VALUE, FLOAT_VALUE, TIMES_VALUE }
static Item noYesValue(boolean noYes) {...}
static Item offOnValue(boolean offOn) {...}
static Item textValue(String text) {...}
static Item floatValue(float value) {...}
static Item timesValue(List<TimesValue> times) {...}
Fields setField;
Object fieldValue;
Value() {}
Value(Fields field, Object value) {setFieldValue(field, value);}
Fields getSetField() { return setField; }
Object getFieldValue() { return fieldValue; }
void setFieldValue(Fields field, Object value) {
// checkType(field, value);
this.setField = field;
this.fieldValue = value;
}
// these do have checks for the set field, types, null, etc
boolean getNoYesValue() { return (Boolean) fieldValue; }
void setNoYesValue(boolean v) { setField = NO_YES_VALUE; fieldValue = v; }
boolean getOffOnValue() { return (Boolean) fieldValue; }
void setOffOnValue(boolean v) { setField = OFF_ON_VALUE; fieldValue = v; }
// ...
}
class TimesValue {
String startTime;
String endTime;
}
With the java classes the following are equivalent:
Value.noYesValue(false);
new Value().setNoYesValue(false);
new Value().setFieldValue(NO_YES_VALUE, false);
new Value(NO_YES_VALUE, false);
Similarly the following are equivalent:
value.getNoYesValue();
(Boolean) value.getFieldValue();
Partially working code (does what I want but only with simple types)
After some more digging and trial and error I've managed to get farther than I have before.
First things first, I flatten out the Value into the Item
interface ItemMixin {
#JsonUnwrapped Value getValue();
}
This hoists all my Value properties so they become part of the Item class, the equivalent of {"value": {"type": "offon", "value": false}} becoming {"type": "offon", "value": false}
Next I need to deal with that Fields enum, to do this I wrote a custom Deserializer which looked like this and register it with a module
new StdDeserializer<Value.Fields>(Value.Fields.class) {
#Override
public Value.Fields deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String type = p.getText();
switch (type) {
case "noyes": return Value.Fields.NO_YES_VALUE;
case "offon": return Value.Fields.OFF_ON_VALUE;
case "float": return Value.Fields.FLOAT_VALUE;
case "text": return Value.Fields.TEXT_VALUE;
case "times": return Value.Fields.TIMES_VALUE;
// ...
}
ctxt.handleWeirdStringValue(Value.Fields.class, type, "Unsupported Value type");
return null;
}
}
Finally I tell the Value type to use the constructor to create the object
static abstract class ValueMixin {
#JsonCreator
ValueMixin(#JsonProperty("type") Value.Fields type, #JsonProperty("value") Object value) {}
}
This gives me 90% of what I need, however the issue I now have is that this only works for primitive types (boolean, String, etc), my times field just gets deserialized as a String causing exceptions in my code.
I've tried using #JsonTypeInfo and #JsonSubTypes on the value parameter, creating a property for that field and putting the annotations there but can't get the creator to correctly resolve the type needed.

Deseralize JSON with Jackson into a list of heterogeneous elements

In my JSON I have an element with the following contents:
{
...
"locations": [
[
{
"location_type": "permanent",
"position": "at",
"accuracy": "exact"
},
"and",
{
"location_type": "permanent",
"position": "in",
"accuracy": "exact"
}
],
"or",
{
"location_type": "temporary",
"position": "at",
"accuracy": "exact"
}
],
...
}
As shown, an element of locations can be:
a location
a logical operator
a list of locations (allowing for complex locations)
I'm getting "Cannot deserialize instance of com.example.processor.transformation.json.Location out of START_ARRAY token".
How can I consume this into a data structure using Jackson?
What I tried so far:
Providing a Location(String logicalOperator) constructor helps for a flat list case. (I basically turn the operator into a special value of Location.)
Adding a Location(List<Location> subLocations) or a Location(Location[] subLocations) constructor doesn't help for this case.
Note: I am not in control of the JSON format so I cannot encode it in a more Jackson-friendly way.
You're going to need a custom de-serializer for that. You can't just add a constructor.
Here's a self-contained example with class Foo, that can be either represented by its own property "foo" : "someString" or by some logical operator "and" or "or", etc. as a String literal, intended to represent a Foo instance whose foo property will be the value of that literal.
This may or may not fit your case exactly, but you can adjust.
In other words:
{"foo": "a"} --> new Foo("a")
"or" --> new Foo("or")
Example
// given...
#JsonDeserialize(using=MyDeserializer.class)
class Foo {
String foo;
public void setFoo(String s) {
foo = s;
}
public String getFoo() {
return foo;
}
public Foo(String s) {
setFoo(s);
}
}
// and custom de-serializer...
class MyDeserializer extends JsonDeserializer<Foo> {
#Override
public Foo deserialize(JsonParser jp, DeserializationContext ct)
throws IOException, JsonProcessingException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
// this JSON object has a "foo" property, de-serialize
// injecting its value in Foo's constructor
if (node.has("foo")) {
return new Foo(node.get("foo").asText());
}
// other case, assuming literal (e.g. "and", "or", etc.)
// inject actual node as String value into Foo's constructor
else {
return new Foo(node.asText());
}
}
}
// here's a quick example
String json = "[{\"foo\": \"a\"}, \"or\", {\"foo\": \"b\"}]";
ObjectMapper om = new ObjectMapper();
List<Foo> list = om.readValue(json, new TypeReference<List<Foo>>(){});
list.forEach(f -> System.out.println(f.foo));
Output
a
or
b
Note for clarity
This represents a very simple example.
In your case, you're probably going to want a polymorphic collection of Location POJOs mixed with LogicalOperator POJOs (or something similar), sharing a common marker interface.
You can then decide what object to de-serialize based on whether the JSON node features contents (i.e. a location) or the JSON node is its contents (e.g. the logical operators).

JSON serialization of an object tree structure

When serializing my POJOs with relationships, I used to create different views for each of my classes. For every class, I created a view Basic, displaying only scalar properties, and Detail including in top of that all my relationships. It looks like this :
public class Stage extends BasicDomainObject {
#JsonView(Views.Stage.Basics.class)
protected String stageType = "";
#JsonView(Views.Stage.Basics.class)
protected String scheduledReleaseGraph = "";
#JsonView(Views.Stage.Details.class)
private Pipeline pipeline;
// ...
}
Then, in my REST api layer, I could serialize the correct view by specifying the right one:
mapper.writerWithView(Views.Stage.Details.class).writeValueAsString(bean);
Now, I had to add a field private Stage parentStage in my Stage class. I'm trying to have an output looking like this with my Details view :
{
"id": 2,
"type": "dev",
"scheduledReleaseGraph" "xxx",
"pipeline" : {
...
},
"parent" : {
"id": 1,
"type": "dev",
"scheduledReleaseGraph" "yyy"
}
}
The goal here is to display the parent association with only one level of depth.
What is the common pattern to achieve this ?
If you use Jackson 2.0, I would look into the JsonIdentityInfo attribute:
https://fasterxml.github.io/jackson-annotations/javadoc/2.0.0/com/fasterxml/jackson/annotation/JsonIdentityInfo.html
This annotation helps you to handle cyclic references when serializing/deserializing.

Replace jsonNode entry with it's child node

I'm trying to convert a JsonNode object returned by my couchbase query like this:
{
"_object": {
"_nodeFactory": {
"_cfgBigDecimalExact": false
},
"_class": "com.fasterxml.jackson.databind.node.ObjectNode",
"_children": {
"Name": {
"_value": "Kapil",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
}
}
},
"_predicate": "http://joebloggs",
"_class": "com.dreamworks.pam.gsplus.entity.GsJsonStore",
"_subject": "joebloggs"
}
To a jsonNode like this:
{
"_object": {
"Name": "Kapil"
},
"_predicate": "http://web.studio.dreamworks.com/data/#user/joebloggs"
"_subject": "joebloggs"
}
My idea is to replace a node with it's child node, for example, replacing _children node in the above example with it's child node Name, replacing _value node with its child etc. Is there anyway I can do this? I've looked at the replace method of the ObjectNode class but it doesn't suit my case. I'm looking for someway to do this without traversing through each node.
PS: The _object field is a schema-less json object, so any solution to do with a POJO wouldn't work for me.

Enunciate not working with Generics

My resource is
#GET
#Path("/items")
public MyCollection<Items> getItems()throws Exception{
//Code to return MyCollection<items>
}
My Item class is
#XmlRootElement
public class Item{
private int id;
private String name;
//Have getters and Setters.
}
And My collection class is Generic as below.
public class MyCollection<T> extends MyBaseCollection{
private java.util.Collection<T> items;
private int count;
}
When i try to generate doc using enunciate. The sample Json has only the item and count and the fields of Item class is not getting reflected.
My sample Json generated is
{
"items" : [ {
}, {
}, {
}, {
}, {
}, {
}, {
}, {
} ],
"count" : ...,
}
How to get id,name inside the Item in the generated sample Json?
Thanks.
This is a limitation that i have run into as well, there is no way to specify #TypeHint on a nested object. To support documentation, consider creating a custom collection that defines "items" as a collection of specific class instead of generic.
If you have an idea of how you would want this to work (using the generic type) I suggest submitting enhancement request to Enunciate team.
I have a similar problem where I am returning a Map and I can't #TypeHint this.

Categories