I have these three classes to represent a directed Graph (for Finite State Machines):
public class Graph {
public Set<Node> nodes;
public Graph(#JsonProperty("nodes") Set<Node> nodes) {
this.nodes = nodes;
}
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "name")
public class Node {
public String name;
public Set<Edge> edges;
public Node(final String name, final Set<Edge> edges) {
this.edges = edges;
this.name = name;
}
public Node(#JsonProperty("name") String name) {
this(name, new HashSet<Edge>());
}
}
public class Edge {
public String name;
public Node successor;
public Edge(#JsonProperty("name") String name,
#JsonProperty("successor") Node successor) {
this.name = name;
this.successor = successor;
}
I want to map them to a JSON Object with help of Jackson 2.3.3
The serialization of a simple Graph works flawlessly without infinite backwards references:
{
"nodes" : [ {
"name" : "Z1",
"edges" : [ {
"name" : "0",
"successor" : {
"name" : "Z0",
"edges" : [ {
"name" : "0",
"successor" : "Z0"
}, {
"name" : "1",
"successor" : "Z1"
} ]
}
}, {
"name" : "1",
"successor" : "Z1"
} ]
}, "Z0" ]
}
But when I want to deserialize this exact JSON, Jackson is giving me an error:
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve Object Id
[Z0] (for [simple type, class graph.Node]) --
unresolved forward-reference? (through reference chain:
graph.Node["edges"]->graph.Node["edges"])
I can see from that, that node Z0 can't be referenced because it is still "under construction", so how do I tell Jackson that it has to construct the nodes first and then add the references in the edges?
Related
I want to serialize a data structure to JSON with Jackson 2.13.3. The serialization works, only it does not work in the way I intend.
I created a simplified example data structure to show what the desired serialization is.
The data structure consists of a main Container, which contains a list of Elements. The elements have some links between them.
In the example I create the following linking structure of the elements:
startTop <--> endTop
^ ^
| |
| |
v v
startBottom <--> endBottom
I want to get the following output
The goal is that the serialization represents the linkage data via the IDs of the linked information. The full serialization of the elements should only occur in the top level list of the container. This does not correspond the order in which jackson encounters the elements during serialization.
{
"allElements": [{
"id": "startBottom",
"successor": "endBottom",
"predecessor": null,
"upperNeighbours": ["startTop", "endTop"],
"lowerNeighbours": null
},
{
"id": "endBottom",
"successor": null,
"predecessor": "startBottom",
"upperNeighbours": null,
"lowerNeighbours": null
},
{
"id": "startTop",
"successor": "endTop",
"predecessor": null,
"upperNeighbours": null,
"lowerNeighbours": ["startBottom"]
},
{
"id": "endTop",
"successor": null,
"predecessor": "startTop",
"upperNeighbours": null,
"lowerNeighbours": ["startBottom"]
}
]
}
I do get the following output
jackson puts the full serialization of an object wherever it encounters the object first, as can be seen in the output I currently get.
{
"allElements" : [ {
"id" : "startBottom",
"successor" : {
"id" : "endBottom",
"successor" : null,
"predecessor" : "startBottom",
"upperNeighbours" : null,
"lowerNeighbours" : null
},
"predecessor" : null,
"upperNeighbours" : [ {
"id" : "startTop",
"successor" : {
"id" : "endTop",
"successor" : null,
"predecessor" : "startTop",
"upperNeighbours" : null,
"lowerNeighbours" : [ "startBottom" ]
},
"predecessor" : null,
"upperNeighbours" : null,
"lowerNeighbours" : [ "startBottom" ]
}, "endTop" ],
"lowerNeighbours" : null
}, "endBottom", "startTop", "endTop" ]
}
Process finished with exit code 0
The java code:
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import java.util.List;
public class Test {
public static void main(String[] args) throws JsonProcessingException {
Element startBottom = new Element("startBottom");
Element endBottom = new Element("endBottom");
Element startTop = new Element("startTop");
Element endTop = new Element("endTop");
startBottom.setSuccessor(endBottom);
startTop.setSuccessor(endTop);
endBottom.setPredecessor(startBottom);
endTop.setPredecessor(startTop);
startBottom.setUpperNeighbours(List.of(startTop, endTop));
startTop.setLowerNeighbours(List.of(startBottom));
endTop.setLowerNeighbours(List.of(startBottom));
Container container = new Container();
container.setAllElements(List.of(startBottom, endBottom, startTop, endTop));
ObjectMapper mapper =
new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ObjectWriter prettyPrintWriter = mapper.writerWithDefaultPrettyPrinter();
System.out.println(prettyPrintWriter.writeValueAsString(container));
}
}
class Container {
public List<Element> getAllElements() {return allElements;}
public void setAllElements(List<Element> allElements) {this.allElements = allElements;}
private List<Element> allElements;
}
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
class Element {
Element(String id) {this.id = id;}
private String id;
// May be null
private Element successor;
// May be null
private Element predecessor;
// May be empty, which for us is the same as being null
private List<Element> upperNeighbours;
// May be empty, which for us is the same as being null
private List<Element> lowerNeighbours;
public String getId() {return id;}
public void setId(String id) {this.id = id;}
public Element getSuccessor() {return successor;}
public void setSuccessor(Element successor) {this.successor = successor;}
public Element getPredecessor() {return predecessor;}
public void setPredecessor(Element predecessor) {this.predecessor = predecessor;}
public List<Element> getUpperNeighbours() {return upperNeighbours;}
public void setUpperNeighbours(List<Element> upperNeighbours) {this.upperNeighbours = upperNeighbours;}
public List<Element> getLowerNeighbours() {return lowerNeighbours;}
public void setLowerNeighbours(List<Element> lowerNeighbours) {this.lowerNeighbours = lowerNeighbours;}
}
Edit: Added that the serialization does work, but not in the intended way.
Just mark your members successor and predecessor in your class Element with annotation #JsonIgnore to prevent endless loop. And your code should work. But I can even suggest something that will make it even simpler. I wrote my own utility that allows you to serialize any class to Json and deserialize from Json back into an instance of a class. It uses Jackson library under the hood. The code that would do the job for you would be as simple as this:
try {
System.out.println(JsonUtils.writeObjectToJsonString(container));
}catch(IOException ioe) {
//Handle exception
}
Where container is the instance of your container class. Here is the Javadoc for class JsonUtils. This class is part of MgntUtils library written and maintained by me. The library can be obtained as Maven artifacts or on Github (including source code and Javadoc)
As #Thomas commented, I have to add before the fields in question:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
The element class then becomes:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
class Element {
Element(String id) {this.id = id;}
private String id;
// May be null
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
private Element successor;
// May be null
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
private Element predecessor;
// May be empty, which for us is the same as being null
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
private List<Element> upperNeighbours;
// May be empty, which for us is the same as being null
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#JsonIdentityReference(alwaysAsId = true)
private List<Element> lowerNeighbours;
public String getId() {return id;}
public void setId(String id) {this.id = id;}
public Element getSuccessor() {return successor;}
public void setSuccessor(Element successor) {this.successor = successor;}
public Element getPredecessor() {return predecessor;}
public void setPredecessor(Element predecessor) {this.predecessor = predecessor;}
public List<Element> getUpperNeighbours() {return upperNeighbours;}
public void setUpperNeighbours(List<Element> upperNeighbours) {this.upperNeighbours = upperNeighbours;}
public List<Element> getLowerNeighbours() {return lowerNeighbours;}
public void setLowerNeighbours(List<Element> lowerNeighbours) {this.lowerNeighbours = lowerNeighbours;}
}
This (with the other code from the question) generates the desired output:
{
"allElements" : [ {
"id" : "startBottom",
"successor" : "endBottom",
"predecessor" : null,
"upperNeighbours" : [ "startTop", "endTop" ],
"lowerNeighbours" : null
}, {
"id" : "endBottom",
"successor" : null,
"predecessor" : "startBottom",
"upperNeighbours" : null,
"lowerNeighbours" : null
}, {
"id" : "startTop",
"successor" : "endTop",
"predecessor" : null,
"upperNeighbours" : null,
"lowerNeighbours" : [ "startBottom" ]
}, {
"id" : "endTop",
"successor" : null,
"predecessor" : "startTop",
"upperNeighbours" : null,
"lowerNeighbours" : [ "startBottom" ]
} ]
}
I am reading one file through which I need to create a specific JSON structure to pass it on UI, but I am unable to write a code for JSON formation. So I created one Java class initialized my class, tried a lot but still now able to find a way, how to form it. Here is my main Java class where I am adding an element like this
public class DynamicListForming {
public static void main(String[] args) {
NodeInfo cab = new NodeInfo("saurabh", "South");
NodeInfo cab1 = new NodeInfo("South", "ZONE1");
NodeInfo cab2 = new NodeInfo("ZONE1", "Street-1");
NodeInfo cab3 = new NodeInfo("ZONE1", "Street-2");
NodeInfo cab4 = new NodeInfo("ZONE1", "Street-3");
NodeInfo cab5 = new NodeInfo("ZONE1", "Street-4");
List<NodeInfo> NodeInfos = new LinkedList<NodeInfo>();
NodeInfos.add(cab);
NodeInfos.add(cab1);
NodeInfos.add(cab2);
NodeInfos.add(cab3);
NodeInfos.add(cab4);
NodeInfos.add(cab5);
}
}
My NodeInfo class looks like this
public class NodeInfo {
private String nodeName;
private String parentName;
private List<NodeInfo> children;
public NodeInfo(String parentName, String nodeName) {
super();
this.parentName = parentName;
this.nodeName = nodeName;
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public List<NodeInfo> getChildren() {
return children;
}
public void setChildren(List<NodeInfo> children) {
this.children = children;
}
}
I need to form JSON structure like below
"nodeInfo": {
"name": "saurabh",
"children": [
{
"name": "SOUTH",
"children": [
{
"name": "Zone-1",
"children": [
{
"name": "Street-1"
},
{
"name": "Street-2"
},
{
"name": "Street-3"
},
{
"name": "Street-4"
}
]
}
]
}
]
}
Any suggestion on how to form this type of JSON structure, I struggled a lot and unable to find a way to dynamically create a list and object to form this structure.
Creating index via MongoShell
db.car.createIndex({brand:1 , model:1 , colour:1 ,fuelTypes:1},{unique:true})
Creating CompoundIndex via spring application
#Document
#CompoundIndex(def = "{ 'brand':1 , 'model':1 , 'colour':1 , 'fuelTypes':1 }",unique = true)
public class Car {
private String brand;
private String model;
private List<FuelType> fuelTypes;
private String colour;
}
I was able to create via Mongo shell but not thourgh spring application.What's wrong in the above code?Are n't they equivalent?
I checked After inserting atleast one document.
Thanks in advance.
Here is a working example I tried (creates a new collection, document and the compound index):
The Car POJO class:
#CompoundIndex(name = "car-cmp-idx", def = "{'brand': 1, 'model': 1}", unique = true)
#Document
public class Car {
private String brand;
private String model;
private String colour;
public Car() {
}
public Car(String brand, String model, String colour) {
this.brand = brand;
this.model = model;
this.colour = colour;
}
// get/set methods. etc...
}
The application code to create a document in the (new) car: collection:
MongoOperations ops = new MongoTemplate(MongoClients.create(), "test");
Car car = new Car("Ford", "Model T", "Black");
ops.insert(car);
The result document verified from the mongo shell:
{
"_id" : ObjectId("5ed46f4960c3f13e5edf43b6"),
"brand" : "Ford",
"model" : "Model T",
"colour" : "Black",
"_class" : "com.example.demo.Car"
}
The indexes:
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.car"
},
{
"v" : 2,
"unique" : true,
"key" : {
"brand" : 1,
"model" : 1
},
"name" : "car-cmp-idx",
"ns" : "test.car"
}
]
I am running the following aggregation lookup query in spring but it doesn't seems to output the same result as one i run in mongo shell. I am guessing mongo shell knows that from:"testModel" collection exists but how do spring know "testModel" exists as we are only pass strings in Aggregation.lookup("testModel"). I can run all other stages fine for example match or project..
// code running in Mongo Shell
db.demoModel.aggregate([{
$lookup:
{
from: "testModel",
localField: "_id",
foreignField: "makeId",
as: "cool"
}
}]
)
// code from spring
public List<DemoModel> getSomeAutos() throws Exception {
LookupOperation lookupStage = Aggregation.lookup(
"testModel",
"_id",
"makeId",
"cool"
);
Aggregation aggregation = Aggregation.newAggregation(lookupStage);
List<DemoModel> demomode = mongoTemplate.aggregate(aggregation, "demoModel", DemoModel.class).getMappedResults();
return demomode;
}
// DemoModel.class
public class DemoModel {
#Id
private String id;
private String value;
private String label;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
//Mongo demoModel Collection
{
"_id" : ObjectId("5a8ee0d815dc17aa32f90f4b"),
"value" : "Mitsubishi",
"label" : "Mitsubishi"
}
{
"_id" : ObjectId("5a8ee0d815dc17aa32f90f4c"),
"value" : "BMW",
"label" : "BMW"
}
// Mongo testModel Collection
{
"_id" : ObjectId("5a8ee393b80c346266f25aba"),
"make" : "Mitsubishi",
"label" : "3000GT",
"value" : "3000GT",
"makeId" : ObjectId("5a8ee0d815dc17aa32f90f4b")
}
{
"_id" : ObjectId("5a8ee393b80c346266f25af8"),
"make" : "BMW",
"label" : "Alpina A2",
"value" : "Alpina A2",
"makeId" : ObjectId("5a8ee0d815dc17aa32f90f4c")
}
// Response i am getting
[
{
"id": "5a8ee0d815dc17aa32f90f4b",
"value": "Mitsubishi",
"label": "Mitsubishi"
},
{
"id": "5a8ee0d815dc17aa32f90f4c",
"value": "BMW",
"label": "BMW"
}
]
// expected response in this format
{
"_id" : ObjectId("5a8ee0d815dc17aa32f90f4b"),
"value" : "Mitsubishi",
"label" : "Mitsubishi",
"cool" : [
{
"_id" : ObjectId("5a8ee393b80c346266f25aba"),
"make" : "Mitsubishi",
"label" : "3000GT",
"value" : "3000GT",
"makeId" : ObjectId("5a8ee0d815dc17aa32f90f4b")
}]
}
Akin to my earlier question, I'm trying to access data in MongoDB using Spring REST.
I have collections of simple Key-Value Pairs and can access those fine.
{
"_id" : ObjectId("5874ab4a19b38fb91fbb234f"),
"roID" : "7ed3f9a6-bb9b-4d16-8d1a-001b7ec40b51",
"Name" : "[REDACTED]"
}
The problem is, these objects are used in another collection that displays a relationship with properties between them, like this:
{
"_id" : ObjectId("5874ab4f19b38fb91fbb6180"),
"[OBJECT CATEGORY A]" : {
"_id" : ObjectId("5874ab4a19b38fb91fbb257b"),
"roID" : "72f8a8b5-71a7-40ac-b1ac-1ffc98a507ba",
"Name" : "[REDACTED]"
},
"[OBJECT CATEGORY B]" : {
"_id" : ObjectId("5874ab4b19b38fb91fbb32a3"),
"roID" : "919446ab-1898-419f-a704-e8c34985f945",
"Name" : "[REDACTED]"
},
"[RELATIONSHIP INFORMATION]" : [
{
"[PROPERTY A]" : [
{
"[VALUE A]" : 5.0
},
{
"[VALUE B]" : 0.0
}
]
},
Properties are somewhere between 8 and 20.
The definition of the first (plain) object in Java looks like this:
#Document(collection="OBJ")
public class Obj {
public Obj(){};
#Id
public String id;
#Field("roID")
public String roID;
#Field("Name")
public String name;
}
The repository class:
#RepositoryRestResource(collectionResourceRel = "OBJ", path = "OBJ")
public interface ObjRepo extends MongoRepository<Obj, String> {
List<Obj> findByName(#Param("name") String name);
}
The question is: how do I access the nested objects? I've tried using LinkedHashMap in place of the Strings for the complex collection, curl only returns "null" when I try to access them. I tried defining a class
public class BITS {
#Id
private String _id;
#Field("roID")
private String roID;
#Field("Name")
private String name;
public BITS(){}
public BITS(String _id,String roID, String name){
this._id = _id;
this.roID = roID;
this.name = name;
}
}
to access these objects, unsuccessfully.
Turns out the class approach was correct, just not well executed.
I've created a plain JSON Collection for testing purposes:
#Document(collection="JSON")
public class JSON {
#Id
public String id;
#Field("squares")
public Square square;
#Field("dots")
public Dot dot;
public JSON(){};
public JSON(String id, Square square,Dot dot){
this.id = id;
this.square = square;
this.dot = dot;
};
}
Square.java
public class Square {
private String id;
private int x;
private int y;
public Square(){};
public Square(String id,int x, int y){
this.id = id;
this.x = x;
this.y = y;
};
public Map<String, Integer> getSquare()
{
Map<String, Integer> res = new HashMap<>();
res.put("x", x);
res.put("y", y);
return res;
}
}
(Dot is the same, just for the test)
So it's just about remodeling the desired response exactly, despite it being in that format in the database already.
If anyone could point me to where I can remove the clutter from the response tho, that would be nice. Currently looks like this:
"_embedded" : {
"JSON" : [ {
"square" : null,
"dot" : {
"dot" : {
"x" : 4,
"y" : 3
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/JSON/58ac466160fb39e5e8dc8b70"
},
"jSON" : {
"href" : "http://localhost:8080/JSON/58ac466160fb39e5e8dc8b70"
}
}
}, {
"square" : {
"square" : {
"x" : 12,
"y" : 2
}
},
"dot" : null,
"_links" : {
"self" : {
"href" : "http://localhost:8080/JSON/58ac468060fb39e5e8dc8b7e"
},
"jSON" : {
"href" : "http://localhost:8080/JSON/58ac468060fb39e5e8dc8b7e"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/JSON"
},
"profile" : {
"href" : "http://localhost:8080/profile/JSON"
}
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}