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" ]
} ]
}
Related
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 have a HATEOAS Spring rest API that connects to a mysql database. I have no control over the database schema and it changes periodically so I am periodically generating the entity classes and updating the service.
I have three files per route. A generated entity class, a controller and a repository file that uses the PagingAndSortingRepository class to automatically serve my entities without much configuration.
Entity class
package hello.models;
import javax.persistence.*;
import java.util.Objects;
#Entity
#Table(name = "animals", schema = "xyz123", catalog = "")
public class AnimalsEntity {
private Integer id;
private String name;
private String description;
#Id
#Column(name = "id", nullable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = true, length = 80)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Basic
#Column(name = "description", nullable = true, length = 255)
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleEntity that = (RoleEntity) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(description, that.description);
}
#Override
public int hashCode() {
return Objects.hash(id, name, description);
}
}
Repository class
package hello.repositories;
#RepositoryRestResource(collectionResourceRel = "animals", path = "animals")
public interface AnimalsRepository extends PagingAndSortingRepository<AnimalEntity, String> {
// Allows /animal/cheetah for example.
AnimalEntity findByName(String name);
// Prevents POST /element and PATCH /element/:id
#Override
#RestResource(exported = false)
public AnimalEntity save(AnimalEntity s);
// Prevents DELETE /element/:id
#Override
#RestResource(exported = false)
public void delete(AnimalEntity t);
}
Controller class
package hello.controllers;
import hello.models.AnimalsEntity;
import hello.repositories.AnimalsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
#RepositoryRestController
#RequestMapping("/animals")
class PrinterController {
#Autowired
private AnimalsRepository animalsRepo;
#RequestMapping("/{name}")
public #ResponseBody
List<AnimalsEntity> findAnimal(#PathVariable(value = "name") String name) {
return animalsRepo.findByName(name);
}
}
I want my HATEOAS API to serve things with the pagination/sorting options. They currently serves responses like..
{
"links" : [ {
"rel" : "first",
"href" : "http://localhost:8080/animals?page=0&size=20",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null
}, {
"rel" : "self",
"href" : "http://localhost:8080/animals{?page,size,sort}",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null
}, {
"rel" : "next",
"href" : "http://localhost:8080/animals?page=1&size=20",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null
}, {
"rel" : "last",
"href" : "http://localhost:8080/animals?page=252&size=20",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null
}, {
"rel" : "profile",
"href" : "http://localhost:8080/profile/animals",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null
}, {
"rel" : "search",
"href" : "http://localhost:8080/animals/search",
"hreflang" : null,
"media" : null,
"title" : null,
"type" : null,
"deprecation" : null
} ],
"content" : [ {
"id" : 1,
"name" : "cheetah",
"description": "xyz
]
},{
"id" : 2,
"name" : "tortise",
"description": "xyz
]
}],
"page" : {
"size" : 20,
"totalElements" : 5049,
"totalPages" : 253,
"number" : 0
}
}
This is awesome. But I need my javascript applications to access the rest api like (GET) /animals/cheetah.
Normally I would change the schema and set #Id on the name property in the entity class but I cannot do that in this instance. I cant change the database schema and eventually I want to dynamically generate these entity classes to allow for easy schema changes.
I've figured out that I can override the endpoint and serve it manually but I lose the pagination/HATEOAS formatting.
[
{
"id": 1,
"name": "cheetah",
"description": "xyz"
},
{
"id": 2,
"name": "tortise",
"description": "xyz"
}
]
How do I accomplish the #Id change without losing the JSON format or changing the entity class?
I found my answer here: https://docs.spring.io/spring-data/rest/docs/current/reference/html/#_customizing_item_resource_uris
You can map other fields as the default lookup.
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
}
}
When producing the schema for a Java type I would like to specify that certain String values (or other types) could be null or "Nullable", for example, if there is a "name" property with this requirement I'd like to see something like this in the output:
"name" : {
"type" : ["string", "null"]
}
Additionally, what if I want to allow a value to be either integer or String? How can Jackson's schema generation be informed about it? I mean something like this:
"id" : {
"type" : ["string", "integer"]
"required" : true
}
But so far the things that I've attempted with Jackson are not working. I have the following method that at runtime will create a JsonSchema for the input type:
public String getJsonSchema(Class<Message> type) throws IOException{
ObjectMapper m = new ObjectMapper();
JsonSchemaGenerator generator = new JsonSchemaGenerator(m);
JsonSchema schema = generator.generateSchema(type);
return m.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
}
Message.java is something like this (I removed the setters to keep the code shorter):
public class Message {
private String name;
private Long id;
#JsonProperty(value = "name", required = false)
#JsonPropertyDescription("User provided name.")
public String getName() {
return name;
}
#JsonProperty(value = "id", required = true)
public Long getId() {
return id;
}
}
The output for the previous bean is this:
{
"type" : "object",
"id" : "urn:jsonschema:spring:integration:quickstart:Message",
"properties" : {
"name" : {
"type" : "string",
"description" : "User provided name."
},
"id" : {
"type" : "integer",
"required" : true
}
}
}
The question is, what annotation or configuration option is required to make "null" one of the possible types for name?
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?