I have 3 main Node types as follows: Node, CNode, QNode. The QNode has can have a list of CNodes which can refer to other CNodes but never to the parent QNode. The QNode is a type of Node but with additional fields, so it extends from Node which is the most basic Node. I am having problems serialization this structure into JSON and to Java objects. There is also a TypeInfo class that can have an optional Node reference. TypeInfo is optional in QNode. When I serialize and print the result object I am only able to get the qNodeId of the QNode and its additional properties but not the child node objects. Here is my attempt. I would really appreciate very much any help with this issue as I have been on it for a while with no success
First the sample json input
{
"qNodes": [
{
"qNodeId": "Q-11122",
"id": "PO11111",
"typeInfo": {
"applicationLevel": "medium",
"required": true
},
"cNodes": [
{
"cNodeId": "Q-11155",
"cNodes": [
{
"qNodeId": "Q-7420",
"typeInfo": {
"applicationLevel": "low",
"assetNode": {
"id": "WQ-222",
"qNode": {
"qNodeId": "Q-0988"
}
},
"required": true
},
"url": "https://example.com ",
"id": "qS111"
}
]
}
]
}
]
}
Next here is the Node class
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class Node {
public String id;
public QNode qNode;
@JsonCreator
public Node(@JsonProperty("qNode") QNode qNode,
@JsonProperty("id") String id){
this.id = id;
this.qNode = qNode;
}
}
Here is the QNode class
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class QNode extends BaseNode{
@JsonProperty("qNodeId")
private String qNodeId;
@JsonProperty("cNodes")
private List<CNode> cNodes;
private TypeInfo typeInfo;
private Map<String, String> additionalElements = new HashMap<>();
@JsonCreator
public QNode(@JsonProperty("qNode") String qNode,
@JsonProperty("id") String id
) {
super(id);
this.qNode = qNode;
}
@JsonAnySetter
public void additionalElements(String property, String value) {
this.additionalElements.put(property, value);
}
}
Here is the CNode class
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CNode {
private String cNodeId;
private QNode qNode;
@JsonCreator
public CNode(@JsonProperty("cNodeId") String cNodeId,@JsonProperty("qNode")QNode
qnode){
this.cNodeId = cNodeId;
this.qNode = qnode;
}
}
And finally the TypeInfo class
public class TypeInfo {
public enum Level {
low,
medium,
high;
}
private Level applicationLevel;
private Node assetNode;
private boolean required;
}
Here is BaseNode:
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
@NoArgsConstructor
public class BaseNode {
protected String id;
protected Map<String, String> additionalProperties = new
HashMap<>();
@JsonCreator
public BaseNode(@JsonProperty("id") String id) {
this.id = id;
}
@JsonAnySetter
public void additionAnswerProperties(String property, String
value) {
additionalProperties.put(property, value);
}
}
Here is the container where the nodes are collected:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class QNodeContainer {
List<QNode> qNodes;
@JsonCreator
public QNodeContainer(@JsonProperty("qNodes") List<QNode>
qNodes){
this.qNodes = qNodes;
}
}
Here is how I read the json into the class:
File resource2 = new ClassPathResource(
"data.json").getFile();
String dataJson = new String(
Files.readAllBytes(resource2.toPath()));
ObjectMapper mapper = new
ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
QNodeContainer qNodes = mapper.readValue(dataJson,
QNodeContainer.class);
I've managed to perform deserialization and subsequent serialization of the JSON you've provided after applying some changes.
The very first advice: before examining serialization of a complex object graph, have a look at smaller pieces. And when you're certain that all custom types can be serialized/deserialized as expected separately, it's time to check the whole thing.
These are the issues I found:
QNode
effectively has two methods annotated with @JsonAnySetter
: additionalElements()
and additionAnswerProperties()
inherited from BaseNode
. That's a huge No-No, this annotation is used to handle all the unmapped properties which can be found in the input JSON. There should at most one such annotation, either of these two maps and methods in QNode
and BaseNode
should be removed. If for some reason you need to separate some of these properties into another map, then they need to be grouped in the JSON-input under a particular well-defined name.
You might want to use @JsonAnyGetter
annotation to flatten the data contained in the map additionalProperties
(I've applied it, pay attention to the comments to spot the changes).
JSON representation of CNode
is inconsistent with your POJO. There's a JSON-array, whilst the field in the class is type QNode
(I've changed it to List<QNode>
).
Some getters were absent (see the comments).
That's the edited version:
@Data
public class Node {
public String id;
public QNode qNode;
@JsonCreator
public Node(@JsonProperty("qNode") QNode qNode,
@JsonProperty("id") String id) {
this.id = id;
this.qNode = qNode;
}
}
@Data
public class QNode extends BaseNode {
@JsonProperty("qNodeId")
private String qNodeId;
@JsonProperty("cNodes")
private List<CNode> cNodes;
private TypeInfo typeInfo;
// private Map<String, String> additionalElements = new HashMap<>(); // see the comment above `additionalElements()`
@JsonCreator
public QNode(@JsonProperty("qNode") String qNode,
@JsonProperty("id") String id
) {
super(id);
this.qNodeId = qNode; // changed from `this.qNode = qNode;` which raised a compilation error
}
// @JsonAnySetter // <- conflicts with @JsonAnySetter on `additionAnswerProperties()` inherited from the super class - there should be only one
// public void additionalElements(String property, String value) {
// this.additionalElements.put(property, value);
// }
}
@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter // <- added
@ToString // <- added
public class CNode {
private String cNodeId;
private List<QNode> qNodes; // <- QNode to List<QNodes>
@JsonCreator
public CNode(@JsonProperty("cNodeId") String cNodeId,
@JsonProperty("cNodes") List<QNode> qNodes) { // <- "qNode" to "cNodes" & QNode to List<QNodes>
this.cNodeId = cNodeId;
this.qNodes = qNodes;
}
}
@ToString // <- added
@Getter // <- added
public class TypeInfo {
private Level applicationLevel;
private Node assetNode;
private boolean required;
public enum Level {
low,
medium,
high;
}
}
@NoArgsConstructor
public class BaseNode {
@Getter // <- added
protected String id;
@JsonAnyGetter // <- added
protected Map<String, String> additionalProperties = new HashMap<>();
@JsonCreator
public BaseNode(@JsonProperty("id") String id) {
this.id = id;
}
@JsonAnySetter
public void additionAnswerProperties(String property, String value) {
additionalProperties.put(property, value);
}
}
@Data
public class QNodeContainer {
private List<QNode> qNodes;
@JsonCreator
public QNodeContainer(@JsonProperty("qNodes") List<QNode> qNodes) {
this.qNodes = qNodes;
}
}
A code demonstrating deserialization/serialization of the JSON sample provided in the question:
String json = """
past your JSON here
""";
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
QNodeContainer qNodes = mapper.readValue(json, QNodeContainer.class);
System.out.println(qNodes);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(qNodes));
Illustration of the discrepancy between the specified JSON representation and your Java-classes: