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.
Related
I am trying to create a Java library to manage the Card.v1 JSON for the Alternative Runtime Google Add-ons functions. I've gotten everything to work, but am running into some problems getting Jackson to wrap classes in the same way that the Google APIs require it.
As an example, they have a Section that has a list of Widgets, which are wrapped Text Paragraphs, Images, or Button Lists. Here is an example JSON:
"sections":[ {
"widgets":[
{
"textParagraph":{
"text":"Your random cat 2:"
}
},
{
"image":{
"imageUrl":"https://cataas.com/cat"
}
},
{
"buttonList":{
"buttons":[
{
"text":"Happy",
},
{
"text":"Sad",
}
]
}
}
]
} ]
I have created a Text class, which looks like this:
#JsonTypeName("textParagraph")
#JsonTypeInfo(include= JsonTypeInfo.As.WRAPPER_OBJECT, use= JsonTypeInfo.Id.NAME)
public class TextParagraph extends BaseWidget
{
String text;
}
If I use the object mapper to write this out on its own, I get the expected JSON:
"textParagraph" : {
"text" : "Testing Text"
}
But if I have a List and then print that, it loses the "textParagraph" wrapping.
{
"widgets" : [ {
"text" : "Testing Text"
}, {
"text" : "Testing Text 2"
}, {
"imageUrl" : "ggole.com/image.png",
"altText" : "Image Text"
} ]
}
Is there a special annotation I'm missing to wrap the list objects? I'd really like to do this without having to use any custom mappers or Wrapper classes.
Does anyone have experience here?
Well, I solved it.
On the base class (BaseWidget), I needed to add the #JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) annotation. With that in place, the list serializes with the wrappers.
I am learning SpringBoot and am doing this coding challenge. So far, I have successfully set up the controller and have done the mapping.
#RestController
#RequestMapping(path="/mydomain")
public class PaymentController {
#RequestMapping(method=RequestMethod.POST, value="/ingest")
public void ingestData(#RequestBody String data) {
System.out.println("ingest Data");
System.out.println(data);
// List<Orders>
// List<Returns>
}
#RequestMapping(method=RequestMethod.GET, value="/query")
public String queryData(#RequestBody String data) {
// look at LIST of order and return..and return something
}
}
The String data is JSON and it contains two different types - Order and Return.
{
"entries": {
{
type: "ORDER",
name: "order_1",
"attributes": {
"owner": "John"
}
},
{
type: "ORDER",
name: "order_2",
"attributes": {
"owner": "Mike",
"buyer": "Bob"
}
// attributes is a key/value pair Map
},
{
type: "RETURN",
name: "return_1",
"attributes": {
"user": "kelly",
"time": "null",
"inputs": [
"USD",
"EUR"
],
"outputs": [
"CAD",
"GBP"
]
}
// attributes is a key/value pair Map
},
}
}
In ingestData(), I want to parse though the json and create 2 lists - one each for orders and returns. In the past, I have dealt with the all the items in the json mapping to the same Java class. How do I parse and map json items into 2 different java classes?
You should probably rethink your REST api setup a bit. It's better to create endpoints based on classes rather than have generic endpoints that process multiple. Although this might look like more work now it will really help you generate more maintainable code. The fact that you now run into this problem where you want an ObjectMapper to resolve to different classes from a single Json is a good indicator that you're going in the wrong direction.
There is a great REST API design best-practices here on Stack available
[JsonString] -some json parsing libraries-> [Collection<Entity>] -.stream().collect(Collectors.groupingBy())-> [Map<String(type),Collection<Entity>>]
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
I have a json object which looks something like this
"price": {
"sale": {
"value": 39.99,
"label": "Now"
},
"regular": {
"value": 69.5,
"label": "Orig."
},
"shippingfee": 0,
"pricetype": "Markdown",
"pricetypeid": 7,
"onsale": true
}
"sale" and "regular" are keywords (unique) are 2 of the many price-types available, and the labels
"Orig" and "Now" are keywords (unique) are 2 of the many labels available.
I am not sure the best data structure to store this price json into the POJO.
can someone please guide me through this ?
I guess your problem is to convert the sale and regular attributes into an uniform representation which probably could use an Enumeration for the unique values. With default mechanism of JSON serilization/deserialization, this could be difficult. I am not sure about the JSON parsing library you are using, in Jackson there is a possibility to register custom serializers and deserializers for fields (using annotations). In this case, whcy tou would do is to register a custom serializer/deserializer and handle the fields of the JSON in the way you want it. You could refer this post
Added the below in response to the comment:
A probable dtructure for the POJO could be as below:
publi class Price{
protected List<PricePointDetails> pricePointDetails;
protected float shippingFee;
protected PriceTypes priceType;
protected priceTypeId;
protected boolean onSale;
}//class closing
public class PricePointDetails{
protected PriceTypes priceType;
protected float value;
protected LabelTypes labelType;
}//class closing
public enumeration PriceTypes{
sale,regular,markdown;
}//enumeration closing
public enumeration LabelTypes{
Orig,Now;
}//enumeration closing
What I have added, is just one way of structuring the data, it could be done in otherways also.
I would like to apologize in advance if this question has been answered before.
Please note that the project is using Spring Boot, Jackson and Hibernate.
Anyway, I forked a project by Jasenko Hadziomeragic. I wanted to know how he was able to save an object, in Jasenko's project, the Product object. In this project, the Product object looks like this (please note that I removed parts of the code to focus on the foreign key) :
#JoinColumn(name = "member_id")
#NotNull
private Long member_id;
I can save a new record by using this JSON:
{
"name" : "Product 2",
"member_id" : "1"
}
This works but from what I have scoured in the internet, it's better to use this instead:
public class Product {
#ManyToOne()
#JoinColumn(name="member_id")
private Member member;
/* A getter/setter for the memeber id*/
public Long getMember_id() {
return this.member.getId();
}
public void setMember_id(Long member_id) {
this.member.setId(member_id);
}
}
I tried to save a new record with the same json above, I get this error:
java.lang.NullPointerException: null
at com.jersey.representations.Product.setMember_id(Product.java:100)
Here's a link to my github that contains modified codes.
I also checked this SO question that is similar to my concern. But what it does it first it sends a select query first to populate the Member object. But that means before I save a record, I have to request another select statement. It's a bit of an overhead, isn't it?
UPDATE
I added this code in the constructor:
public Product() {
this.member = new Member();
}
I did fix my issue but is this the correct way of dealing with my concern?
UPDATE 2
What I am trying to avoid is sending this JSON:
[
{
"id": 1,
"name": "Derp",
"currency": "599",
"regularPrice": 1203,
"discountPrice": 59,
"member": {
"id": 1,
"firstName": "Paul",
"lastName": "Andrews",
"email": "myemail#email.com"
}
},
{
"id": 2,
"name": "Another product ",
"currency": "909",
"regularPrice": 1203,
"discountPrice": 59,
"member": {
"id": 1,
"firstName": "Paul",
"lastName": "Andrews",
"email": "myemail#email.com"
}
},
{
"id": 3,
"name": "Another product 1",
"currency": "909",
"regularPrice": 1203,
"discountPrice": 59,
"member": {
"id": 1,
"firstName": "Paul",
"lastName": "Andrews",
"email": "myemail#email.com"
}
}
]
As you can see, the member keeps repeating whereas I can just send the memberId.
Another link to a similar question I posted
Please, try to learn Hibernate using a simply console application with Model and Product persistents. You don't need #JoinColumn for products but #OneToMany with mappedBy property. You can refer User as an analogue of Member and UserFriend as Product. In every Product you need to have foreign key to User — add products to member this way. For experiments you can use this or this.
Ok so I finally got to make it work. I don't know where to pinpoint but I can definitely say that it's not JSON marshalling by Jackson issue.
Here's my solution:
First, I can't find a solution but to add this to class that has the foreign key:
#Entity
public class Product {
public Product() {
this.member = new Member();
}
#ManyToOne()
#JoinColumn(name="blargh")
private Member member;
/** Jackson can't instantiate and object automatically so you have to help it **/
/** By creating the object right after Product is initialzed by some other code **/
public Long getMobileNum() {
return this.member.getMobileNum();
}
public void setMobileNum(Long mobileNum) {
this.member.setMobileNum(mobileNum);
}
}
Next step to look for it the Id annotation in the other class.
#Entity
public class Member {
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Id
private Long mobileNum;
}
I intentionally made the annotations wrong to prove a point. In the Product class, it's foreign key that links it to the Member class is mobileNum. If mobileNum or any foreign key is annotated with #Id, Hibernate will bind it properly and won't have any issues saving the object.
But if the foreign key is not annotated with #Id, Hibernate (even if Jackson has properly mapped the JSON to the object), it will not bind it properly resulting in null values. Google search keyword: object references an unsaved transient instance
Adding Cascade is not a good solution in my opinion as it just creates more records in the Member class. It will keep on creating EVEN if the record already exists! (Trying putting a unique constraint on mobileNum and remove the #Id annotation.
Overall, this week in Hibernate has really burned me out. If you're a developer who had experience in optimizing an existing SQL and just want to turn it into code, stay away from Hibernate. Use MyBatis, even if it has a lot of boilerplate, it's much more satisfying to create your SQL by hand than by Hibernate.
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.