JsonView serialization that depends on some condition/property name etc - java

One of my classes has 3 properties of same type. Now I'm trying to serialize it do JSON, but one of those properties needs to be serialized differently - basically one of those properties is "internal" and I need only id of it, the rest of them must be fully serialized.
What I came so far:
#NoArgsConstructor #AllArgsConstructor #Data
public static class Id {
#JsonView(View.IdOnly.class) private long id;
}
#NoArgsConstructor #AllArgsConstructor #Data
public static class Company extends Id {
#JsonView(View.Tx.class) private String name;
#JsonView(View.Tx.class) private String address;
}
#NoArgsConstructor #AllArgsConstructor #Data
public static class Transaction {
#JsonView(View.Tx.class) private Company from;
#JsonView(View.Tx.class) private Company to;
#JsonView(View.IdOnly.class) private Company createdBy;
}
public static class View {
public interface Tx extends IdOnly {}
public interface IdOnly {}
}
And quick test for it:
#Test
void test() throws JsonProcessingException {
Company s = new Company("Source", "address_from");
Company d = new Company("Destination", "address_to");
final Transaction t = new Transaction(s, d, s);
final ObjectMapper m = new ObjectMapper();
System.out.println(m.writerWithDefaultPrettyPrinter().withView(View.Tx.class).writeValueAsString(t));
}
And output is:
{
"from" : {
"id" : 0,
"name" : "Source",
"address" : "address_from"
},
"to" : {
"id" : 0,
"name" : "Destination",
"address" : "address_to"
},
"createdBy" : {
"id" : 0,
"name" : "Source",
"address" : "address_from"
}
}
Now, question, how can I customize serialization of createBy property? I need following output:
{
"from" : {
"id" : 0,
"name" : "Source",
"address" : "address_from"
},
"to" : {
"id" : 0,
"name" : "Destination",
"address" : "address_to"
},
"createdBy" : {
"id" : 0,
}
}

Oh, I think that answer for that is very simple:
Mark createdBy field with #JsonSerialize(using = CS.class)
Implement custom serializer as follows:
public static class CS extends JsonSerializer<Company> {
#Override
public void serialize(Company company, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException {
jgen.writeStartObject();
jgen.writeNumberField("id", company.getId());
jgen.writeEndObject();
}
}

Related

Recommendation for resetting data in json object using Java

I need a recommendation for this situation.
I have a json object in string format that will have pattern like this:
{
"productCard" : {
"productA" : {
"state" : "Y",
"desc" : "AAA",
"someProp" : 112
},
"productB" : {
"state" : "X",
"desc" : " BBB ",
"listSomeThing" : [
{
"p1" : 1,
"p2" : "2"
},
{
"p2" : "3"
}
]
}
// PRODUCT CAN ADD MORE IN FUTRE
// ALSO CAN HAVE OTHER OBJECT TYPE
}
// THIS CAN HAVE OTHER OBJECT THAT MAY BE NON RELATE INFORMATION WITH PRODUCT CARD
}
and then this will be parsed to an object like this:
class Product {
protected String state
protected String desc
}
class SomeThing {
private int p1
private String p2
}
class ProductA extend Product {
private int someProp
}
class ProductB extend Product {
private List<SomeThing> listSomeThing
}
class ProductCard {
private ProductA prodctA
private ProductB productB
}
class BaseObject {
private ProductCard productCard
}
If I need to reset some field value in each product, and then parse to string format again, should I:
(1) create a new function in Product and then override in some child class for extra method:
class Product {
void reset(){
this.state = "X"
this.desc = ""
}
}
class productB extend Product {
#override
void reset(){
super.reset()
this.listSomeThing = new ArrayList<>()
}
}
and in base object create new function:
class ProductCard {
private ProductA productA
private ProductB productB
void resetAllProduct(){
this.productA.reset()
this.productB.reset()
}
}
class BaseObject {
private ProductCard productCard
void resetAllProductCard(){
this.productCard.resetAllProduct()
}
}
then call BaseObject.resetAllProductCard() where business needs to reset?
(2) create new function in business class? Or some util class:
void reset(ProdctCard productCard){
ProductA productA = productCard.getProductA();
productA.setState("X")
productA.setDesc("")
ProductB productB = productCard.getProdctB();
productB.setState("X")
productB.setDesc("")
productB.setListSomeThing(new ArrayList<>())
}
(3) another approach?
I would use Jackson Project for that job:
public String reset(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(json);
JsonNode productCardNode = jsonNode.get("productCard");
productCardNode.forEach(node -> ((ObjectNode) node).put("state", "X").put("desc", ""));
ObjectNode productBNode = (ObjectNode) productCardNode.get("productB");
productBNode.putArray("listSomeThing");
return jsonNode.toPrettyString();
}
Then:
String jsonReseted = reset(json);
System.out.println(jsonReseted);
Output:
{
"productCard" : {
"productA" : {
"state" : "X",
"desc" : "",
"someProp" : 112
},
"productB" : {
"state" : "X",
"desc" : "",
"listSomeThing" : [ ]
}
}
}

Grouping in Spring Data MongoDB returns NULL _id when mapping to a class with composite key

I want to aggregate a collection of documents that match certain conditions, group them and map the output to a different class object. The aggregation works fine and I get the expected total but the _id field is always NULL.
I'm using spring-data-mongodb 2.1.11 and MongoDB 3.6.
This is the class to be aggregated:
#Document
public class LegOrder {
public static class Key {
#Indexed
long itemId;
long transactionId;
...
}
#Id
private Key id;
#Indexed
private long brandId;
private int units;
...
}
This is the aggregation output class:
#Document
public class ItemAggregation {
public static class Key {
#Indexed
long itemId;
#Indexed
long brandId;
}
#Id
private Key id;
private long total;
...
}
My aggregation method:
public ItemAggregation aggregate(long itemId, long brandId) {
MatchOperation matchStage = Aggregation.match(new Criteria().andOperator(
Criteria.where("id.itemId").is(itemId),
Criteria.where("brandId").is(brandId)
));
GroupOperation groupStage = Aggregation.group("id.itemId", "brandId")
.sum("units").as("total")
...
;
Aggregation aggregation = Aggregation.newAggregation(matchStage, groupStage);
return mongoTemplate.aggregate(aggregation, LegOrder.class, ItemAggregation.class).getUniqueMappedResult();
}
The executed query in MongoDB:
[
{
"$match": {
"$and": [
{ "_id.itemId": 1 },
{ "brandId": 2}
]
}
},
{
"$group": {
"_id": {
"itemId": "$_id.itemId",
"brandId": "$brandId"
},
"total": { "$sum": "$units" }
}
}
]
If I run this query in the mongo shell the _id field is properly populated.
Any idea how to achieve it?
Thank you
Sorry for the late response. I faced this issue now and found this solution.
My aggregation output in console is
{
"_id" : {
"ownerId" : BinData(3,"xkB0S9Wsktm+tSKBruv6og=="),
"groupbyF" : "height"
},
"docs" : [
{
"id" : ObjectId("5fe75026e211c50ef5741b31"),
"aDate" : ISODate("2020-12-26T15:00:51.056Z"),
"value" : "rrr"
}
]
}
{
"_id" : {
"ownerId" : BinData(3,"AAAAAAAAAAAAAAAAAAAAAA=="),
"groupbyF" : "weight"
},
"docs" : [
{
"id" : ObjectId("5fe6f702e211c50ef5741b2f"),
"aDate" : ISODate("2020-12-26T08:40:28.742Z"),
"value" : "55"
},
{
"id" : ObjectId("5fe6f6ade211c50ef5741b2e"),
"aDate" : ISODate("2020-12-26T08:38:58.098Z"),
"value" : "22"
}
]
}
The mapping that worked for me
import lombok.Data;
#Data
public class AggregationLatest2Type{
private String ownerId;
private String key;
private List<Doc> docs;
#Data
public class Doc{
private String _id;
private Date aDate;
private String value;
}
}

jackson jsonSchema: How to set type object for property (JsonRawValue)

I use jackson and jackson-module-jsonSchema to deserialize json and generate json schema (on fly) to validate json by json-schema-validator.
I have a class with field "payload". This field should contain raw json, because there can be any properties, which client needs. For example:
{
"author": "test",
"payload": {
"title": "Test title"
}
}
I expect that field payload will have type "object" in schema, but it's type "string". How should I tell to scheme generator to make it object???
Class:
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.JsonNode;
public class Book {
private String author;
private Object payload;
#JsonRawValue
public Object getPayload() {
return payload;
}
public void setPayload(JsonNode node) {
this.payload = node;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
#Override
public String toString() {
return "Book{" +
"author='" + author + '\'' +
", payload=" + payload +
'}';
}
}
My test:
#Test
public void generateSchemaBook() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule());
JsonSchemaGenerator schemaGen = new JsonSchemaGenerator(mapper);
final JsonSchema jsonSchema = schemaGen.generateSchema(Book.class);
jsonSchema.set$schema("http://json-schema.org/draft-03/schema#");
final String schema = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
/*
{
"type" : "object",
"id" : "urn:jsonschema:ru:infon:mas:protocol:Book",
"$schema" : "http://json-schema.org/draft-03/schema#",
"properties" : {
"author" : {
"type" : "string",
"required" : true
},
"payload" : {
"type" : "string",
"required" : true
}
}
}
*/
System.out.println(schema);
String testJson = "{\"author\":\"test\",\"payload\":{\"title\":\"Test title\"}}";
Book book = mapper.readValue(testJson, Book.class);
System.out.println(book);
assertEquals("{\"title\":\"Test title\"}", book.getPayload().toString());
ProcessingReport validate = JsonSchemaFactory.byDefault().getJsonSchema(JsonLoader.fromString(schema)).validate(JsonLoader.fromString(testJson));
assertTrue(validate.isSuccess());
}
I did not find solution to do this on fly and decided to generate json schema once, put it to file and load.

Convert XML key value mapping to JSON object

I currently have an object which is a key-value pair that I have converted from XSD to POJO using JAXB and I tried using Jackson 2.x to get the JSON output for the POJO. This JSON output looks like:
[ {
"key" : "key1",
"value" : 1
}, {
"key" : "key2",
"value" : "2"
}, {
"key" : "key3",
"value" : [ ]
} ]
Currently my XSD generated POJO looks like:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "MapEntryType", propOrder = {
"value"
})
public class MapEntryType {
#XmlElement(required = true)
protected Object value;
#XmlAttribute(name = "key", required = true)
protected String key;
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"mapEntries"
})
#XmlRootElement(name = "EventsSearchResponse")
public class EventsSearchResponse {
#XmlElement(name = "MapEntry")
protected List<MapEntryType> mapEntries;
}
I would like to generate the map as a simple JSON object:
{ "key1" : 1, "key2" : "2", "key3" : []}
I went over the annotations that are available in Jackson from http://wiki.fasterxml.com/JacksonAnnotations but I have not been able to find a way to perform this type of conversion. Any help regarding this would be really appreciated! Thanks.
I was able to achieve the desired results with a custom serializer (written as inner class for convinience):
public static class EventsSearchResponseSerializer extends JsonSerializer<EventsSearchResponse>
{
#Override
public void serialize(EventsSearchResponse res, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException
{
gen.writeStartObject();
for (MapEntryType t : res.mapEntries) {
gen.writeObjectField(t.key, t.value);
}
gen.writeEndObject();
}
}
added the proper annotation to the POJO:
#JsonSerialize(using = EventsSearchResponseSerializer.class)
#XmlRootElement(name = "EventsSearchResponse")
public static class EventsSearchResponse {
#XmlElement(name = "MapEntry")
public List<MapEntryType> mapEntries;
}
calling the Jackson mapper:
public static void main(String[] args)
{
EventsSearchResponse r = new EventsSearchResponse();
r.mapEntries = new ArrayList<>();
MapEntryType t = new MapEntryType();
t.key = "key1";
t.value = new Integer(1);
r.mapEntries.add(t);
t = new MapEntryType();
t.key = "key2";
t.value = new Integer(2);
r.mapEntries.add(t);
t = new MapEntryType();
t.key = "key2";
t.value = new String[0];
r.mapEntries.add(t);
try {
System.out.println(new ObjectMapper().writeValueAsString(r));
} catch (Exception e) {
e.printStackTrace();
}
}
gives result:
{"key1":1,"key2":2,"key2":[]}

Order of JSON objects using Jackson's ObjectMapper

I'm using ObjectMapper to do my java-json mapping.
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
ow.writeValue(new File( fileName +".json"), jsonObj);
this is my java class:
public class Relation {
private String id;
private String source;
private String target;
private String label;
private List<RelAttribute> attributes;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public void setAttributes(List<RelAttribute> attributes) {
this.attributes = attributes;
}
public List<RelAttribute> getAttributes() {
return attributes;
}
}
this is what I get:
{
"id" : "-75da69d3-79c8-4000-a3d8-b10350a57a7e",
"attributes" : [ {
"attrName" : "ID",
"attrValue" : ""
}, {
"attrName" : "Description",
"attrValue" : "Primary Actor"
}, {
"attrName" : "Status",
"attrValue" : ""
} ],
"label" : "new Label",
"target" : "-46b238ac-b8b3-4230-b32c-be9707f8b691",
"source" : "-daa34638-061a-45e0-9f2e-35afd6c271e0"
}
So my question now is, how can I get this json output:
{
"id" : "-75da69d3-79c8-4000-a3d8-b10350a57a7e",
"label" : "new Label",
"target" : "-46b238ac-b8b3-4230-b32c-be9707f8b691",
"source" : "-daa34638-061a-45e0-9f2e-35afd6c271e0",
"attributes" : [ {
"attrName" : "ID",
"attrValue" : ""
}, {
"attrName" : "Description",
"attrValue" : "Primary Actor"
}, {
"attrName" : "Status",
"attrValue" : ""
} ]
}
I want it with same order as in my java declaration. Is there a way to specify it ? Maybe with annotations or stuff like that ?
#JsonPropertyOrder({ "id", "label", "target", "source", "attributes" })
public class Relation { ... }
Do you know there is a convenient way to specify alphabetic ordering?
#JsonPropertyOrder(alphabetic = true)
public class Relation { ... }
If you have specific requirements, here how you configure custom ordering:
#JsonPropertyOrder({ "id", "label", "target", "source", "attributes" })
public class Relation { ... }
The ordering of fields within a generated .class is indeterminate, so you can't count on that.
If you want specific ordering per class then you'll need to use the one of the approaches specified in other answers.
If you want everything to default to alphabetical ordering (e.g. for consistency in how the JSON is structured) then you can configure the ObjectMapper like this:
ObjectMapper mapper = new ObjectMapper();
mapper.setConfig(mapper.getSerializationConfig()
.with(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY));
For more consistent JSON consider also adding:
.with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
One advantage of this approach is that you don't have to modify each class being serialized.
I discovered a third way today in case alphabetic is not your desired sorting order. It turns out adding a #JsonProperty annotation on a field places it last when writing. I discovered that when I wanted to specify a property name which did not conform to java naming conventions.
By Adding an index attribute you can define the order. Lowest index is placed first.
#JsonProperty(index=20)
String prop1;
#JsonProperty(index=10)
String prop2;
Would render:
{"prop2": "valueProp2", "prop1": "valueProp1"}
You can use #XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "response", propOrder = { "prop1", "prop2",
"prop3", "prop4", "prop5", "prop6" }).
#JsonPropertyOrder requires a new jar to be added.
As per this documentation, you can configure Jackson2ObjectMapperBuilder globally. This class is available in spring-web dependency.
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
return builder;
}
Also, you can use #JsonProperty(index) to determine the order in inherited classes as well.
class animal {
#JsonProperty(index=2)
int p1;
#JsonProperty(index=3)
int p2;
}
class cat extends animal{
#JsonProperty(index=1)
int p3;
}

Categories