Mapping dynamic json object from parameter to POJO - java

I am using resttemplate to get a json response body, which looks like this :
{
"entities": {
"Q11649": {
"type": "item",
"id": "Q11649",
"sitelinks": {
"enwiki": {
"site": "enwiki",
"title": "Nirvana (Band)",
"badges": []
},
..
The object "Q11649" is based on a url parameter called ids, for example :
https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&props=sitelinks&ids=Q11649
this part at the end ids=Q11649
I only want the title value from under enwiki key, however the ids is dynamic so I can't create a POJO to map it to that id.
How can create a dynamic resttemplate method to extract only the title from this json structure?
I have a Wikidata class that looks like this :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Wikidata {
#JsonProperty("entities")
private Entity entity;
public Wikidata () {
}
public Wikidata(Entity entity) {
this.entity = entity;
}
public Entity getEntities() {
return entity;
}
public void setEntities(Entity entity) {
this.entity = entity;
}
}
After reading a few articles, I realise that a map is probably the best way to go, this is how my entity class looks like :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Entity {
#JsonProperty()
private Map<Object, Object> data;
public Entity () {
data = new HashMap<>();
}
public Map<Object, Object> getData(){
return data;
}
#JsonAnySetter
public void add(String property, String value){
data.put(property, value);
}
}
My Sitelinks class :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Sitelinks {
private Enwiki enwiki;
public Sitelinks () {
}
public Sitelinks(Enwiki enwiki) {
this.enwiki = enwiki;
}
public Enwiki getEnwiki() {
return enwiki;
}
public void setEnwiki(Enwiki enwiki) {
this.enwiki = enwiki;
}
}
And my Enwiki class :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Enwiki {
private String title;
public Enwiki () {
}
public Enwiki(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
How can I use this map to link between the dynamic id and my sitelinks class so that I can get the title value?

Related

How to merge two different Mono to produce final response in java reactive

In getProductById method I am trying to construct Mono<OrderResponse> from
Mono<Book> and Mono<Container> like shown below. The issue is response structure what this method is returning different from what I should get.
public Mono<OrderResponse> getProductById(Integer id) {
Mono<Book> monoBook=Mono.just(id).
flatMap(ops.service(BookingDomainService.class)::getProductById);
Mono<Container> monoCon=Mono.just(id).
flatMap(ops.service(BookingDomainService.class)::getContainerById);
OrderResponse or=new OrderResponse(monoBook,monoCon);
return Mono.just(or);
}
Structure of BOOK, Container and Response class are below.
My Book Class :
#Data
#AllArgsConstructor
#Builder
#NoArgsConstructor
public class Book implements Serializable {
#Id
private Integer id;
#Column
private String name;
#Column
private Long price;
}
Container Class:
public class Container {
private String containerName;
private String description;
public String getContainerName() {
return containerName;
}
public void setContainerName(String containerName) {
this.containerName = containerName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
My Response class:
public class OrderResponse {
private Mono<Book> orderMono;
private Mono<Container> orderContainerMono;
public Mono<Book> getOrderMono() {
return orderMono;
}
public void setOrderMono(Mono<Book> orderMono) {
this.orderMono = orderMono;
}
public Mono<Container> getOrderContainerMono() {
return orderContainerMono;
}
public void setOrderContainerMono(
Mono<Container> orderContainerMono) {
this.orderContainerMono = orderContainerMono;
}
public OrderResponse(Mono<Book> orderMono, Mono<Container> orderContainerMono) {
this.orderMono = orderMono;
this.orderContainerMono = orderContainerMono;
}
}
Final response that is being formed from method getProductById(Integer id) is
{
"orderMono": {
"scanAvailable": true
},
"orderContainerMono": {
"scanAvailable": true
}
}
but I need final response as:
I need final response as below json. How to achieve it.
Response:
{
"Book": {
"id": 12,
"name": "pn",
"price": 128
},
"Container": {
"containerName": " Cname",
"description": "diesc"
}
}
You can use Mono.zip to aggreate the results of multiple Monos into a single one that will be fulfilled when all of the given Monos have produced an item.
public final class OrderResponse {
private final Book book;
private final Container container;
public OrderResponse(Book book, Container container) {
this.book = book;
this.container = container;
}
// ...
}
public Mono<OrderResponse> getProductById(Integer id) {
// replace these lines with your actual calls
Mono<Book> bookMono = Mono.just(new Book(1, "Book 1", 1L));
Mono<Container> containerMono = Mono.just(new Container("A", "B"));
return Mono.zip(bookMono, containerMono)
.map(tuple -> new OrderResponse(tuple.getT1(), tuple.getT2()));
}
If you want to return a OrderResponse object directly instead of it being wrapped in a Mono, you can check out the Mono#block method.

Can not store list of objects as single column JSON

I'm trying to store a whole array of object into one field on my oracle database, I'm referring to the solution on this question, but it kept giving me Can not set java.lang.String field xxx.demo.Models.Sensors.amplitudos to xxx.demo.Models.Sensors error, I have checked the JSON body and the entity class, but I cannot find the mistake.
Here is my code.
entity
#Entity
#Table(name = "SENSOR")
public class Sensor implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "TIMERECEIVED")
private Timestamp timereceived;
#Column(name = "SENSORS")
private Sensors[] sensors;
#Column(name = "LOC")
private String location;
public Sensor() {
}
public Sensor(Timestamp timereceived, Sensors[] sensors, String location) {
this.timereceived = timereceived;
this.sensors = sensors;
this.location = location;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Timestamp getTimereceived() {
return timereceived;
}
public void setTimereceived(Timestamp timereceived) {
this.timereceived = timereceived;
}
public Sensors[] getSensors() {
return sensors;
}
public void setSensors(Sensors[] sensors) {
this.sensors = sensors;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
Sensors class
#Embeddable
public class Sensors {
private String amplitudos;
private Double displacement;
private String frequencies;
private Integer sensorId;
public Sensors() {
}
public Sensors(String amplitudos, Double displacement, String frequencies, Integer sensorId) {
this.amplitudos = amplitudos;
this.displacement = displacement;
this.frequencies = frequencies;
this.sensorId = sensorId;
}
public String getAmplitudos() {
return amplitudos;
}
public void setAmplitudos(String amplitudos) {
this.amplitudos = amplitudos;
}
public Double getDisplacement() {
return displacement;
}
public void setDisplacement(Double displacement) {
this.displacement = displacement;
}
public String getFrequencies() {
return frequencies;
}
public void setFrequencies(String frequencies) {
this.frequencies = frequencies;
}
public Integer getSensorId() {
return sensorId;
}
public void setSensorId(Integer sensorId) {
this.sensorId = sensorId;
}
}
my JSON body
{
"timereceived": "2022-11-29T12:04:42.166",
"sensors": [
{
"amplitudos": "a1#a2#a3#a4",
"displacement": 0.002,
"frequencies": "f1#f2#f3#f4",
"sensorid": 1
},
{
"amplitudos": "a1#a2#a3#a4",
"displacement": 0.002,
"frequencies": "f1#f2#f3#f4",
"sensorid": 2
},
{
"amplitudos": "a1#a2#a3#a4",
"displacement": 0.002,
"frequencies": "f1#f2#f3#f4",
"sensorid": 3
},
{
"amplitudos": "a1#a2#a3#a4",
"displacement": 0.002,
"frequencies": "f1#f2#f3#f4",
"sensorid": 4
}
],
"location": "lokasi"
}
my controller
#PostMapping("/sendData")
public ResponseEntity sendData(#RequestBody Sensor sensor) {
Sensor newSensor = sensorRepository.save(sensor);
System.out.println(newSensor);
return ResponseEntity.ok("Sensor received");
}
I have tried checking every possible solution and the problem is not fixed, my expectation is the data stored into 1 column for the sensors field in the JSON body.
The problem is with the JPA mapping, not with the Controller, I think.
You're using #Embeddable, which normally result in a set of columns in your main table. If it's a collection of #Embeddable objects, you could map it to a separate table with foreign keys, using #ElementCollection.
However, you want to store the collection of sensors as a single JSON string in a single column in your main table. For that, you do not need the #Embeddable annotation. You need to write a custom convertor to convert the collection of sensors to JSON.
public class SensorsConverter implements AttributeConverter<List<Sensors>, String> {
private final ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convertToDatabaseColumn(List<Sensors> sensors) {
return objectMapper.writeValueAsString(sensors);
}
#Override
public List<Sensors> convertToEntityAttribute(String sensorsJSON) {
return objectMapper.readValue(sensorsJSON, new TypeReference<List<Sensors>>() {});
}
}
Then you can use it in your entity class:
#Column(name = "SENSORS")
#Convert(converter = SensorsConverter.class)
private List<Sensors> sensors;

Deserialize dynamic json using jackson JsonTypeInfo property as ENUM?

I am trying to get java object from dynamic JSON.
One Important point these given classes are from third party API.
#JsonTypeInfo(
use = Id.NAME,
include = As.PROPERTY,
property = "nodeType"
)
#JsonSubTypes({ #Type(
name = "Filter",
value = Filter.class
), #Type(
name = "Criterion",
value = Criterion.class
)})
public abstract class Node {
public Node() {
}
#JsonIgnore
public EvaluationResult evaluate(Map<UUID, List<AnswerValue>> answers) {
Evaluator evaluator = new Evaluator();
return evaluator.evaluateAdvancedLogic(this, answers);
}
}
Filter.java
#JsonInclude(Include.NON_NULL)
#JsonPropertyOrder({"evaluationType", "filters"})
public class Filter extends Node {
#JsonProperty("evaluationType")
private EvaluationType evaluationType;
#NotNull
#JsonProperty("filters")
#Valid
private List<Node> filters = new ArrayList();
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap();
public Filter() {
}
#JsonProperty("evaluationType")
public EvaluationType getEvaluationType() {
return this.evaluationType;
}
#JsonProperty("evaluationType")
public void setEvaluationType(EvaluationType evaluationType) {
this.evaluationType = evaluationType;
}
#JsonProperty("filters")
public List<Node> getFilters() {
return this.filters;
}
#JsonProperty("filters")
public void setFilters(List<Node> filters) {
this.filters = filters;
}
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
}
Criterion.java
#JsonInclude(Include.NON_NULL)
#JsonPropertyOrder({"fieldSourceType", "fieldCategoryName", "sequenceNumber", "fieldName", "values", "operator", "fieldId"})
public class Criterion extends Node {
#JsonProperty("fieldSourceType")
private FieldSourceType fieldSourceType;
#JsonProperty("fieldCategoryName")
private String fieldCategoryName;
#NotNull
#JsonProperty("sequenceNumber")
private Long sequenceNumber;
#JsonProperty("fieldName")
private String fieldName;
#JsonProperty("values")
#Valid
private List<String> values = new ArrayList();
#JsonProperty("operator")
#Valid
private Operator operator;
#NotNull
#JsonProperty("fieldId")
private UUID fieldId;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap();
public Criterion() {
}
#JsonProperty("fieldSourceType")
public FieldSourceType getFieldSourceType() {
return this.fieldSourceType;
}
#JsonProperty("fieldSourceType")
public void setFieldSourceType(FieldSourceType fieldSourceType) {
this.fieldSourceType = fieldSourceType;
}
#JsonProperty("fieldCategoryName")
public String getFieldCategoryName() {
return this.fieldCategoryName;
}
#JsonProperty("fieldCategoryName")
public void setFieldCategoryName(String fieldCategoryName) {
this.fieldCategoryName = fieldCategoryName;
}
#JsonProperty("sequenceNumber")
public Long getSequenceNumber() {
return this.sequenceNumber;
}
#JsonProperty("sequenceNumber")
public void setSequenceNumber(Long sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
#JsonProperty("fieldName")
public String getFieldName() {
return this.fieldName;
}
#JsonProperty("fieldName")
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
#JsonProperty("values")
public List<String> getValues() {
return this.values;
}
#JsonProperty("values")
public void setValues(List<String> values) {
this.values = values;
}
#JsonProperty("operator")
public Operator getOperator() {
return this.operator;
}
#JsonProperty("operator")
public void setOperator(Operator operator) {
this.operator = operator;
}
#JsonProperty("fieldId")
public UUID getFieldId() {
return this.fieldId;
}
#JsonProperty("fieldId")
public void setFieldId(UUID fieldId) {
this.fieldId = fieldId;
}
}
The json used to conversion is this.
{
"evaluationType":"AND",
"nodeType":"Criterion",
"Criterion":[
{
"fieldName":"sdada",
"values":"sdad",
"operator":{
"operatorType":"Equals"
}
},
{
"nodeType":"Criterion",
"fieldName":"dasa",
"values":"das",
"operator":{
"operatorType":"Equals"
}
},
{
"nodeType":"Criterion",
"fieldName":"dada",
"values":"dads",
"operator":{
"operatorType":"Equals"
}
}
]
}
The problem is that deserialization of this JSON fails with following error:
{
"message": "Class com.cvent.logic.model.Criterion is not assignable to com.cvent.logic.model.Filter"
}
The first part of the JSON is wrong
{
"evaluationType":"AND",
"nodeType":"Criterion",
"Criterion":[
It says that the type is Criterion but it has evaluationType from Filter.
Also, probably "Criterion" : [ should be "filters" : [

DynamoDB mapping List of Enum

Mapping an enum class in to DynamoDB object is really simple by using Custom Marshall. But how to map a List of Enum?
Enum class
public enum Transport {
SMS,EMAIL,ALL;
}
DynamoDB mapper
public class Campaign{
private List<Transport> transport;
#DynamoDBAttribute(attributeName = "transport")
public List<Transport> getTransport() {
return transport;
}
public void setTransport(List<Transport> transport) {
this.transport = transport;
}
}
DynamoDBMarshaller is deprecated.
Use DynamoDBTypeConverter instead.
Example:
Enum class
public static enum ValidationFailure {
FRAUD, GENERAL_ERROR
}
DynamoDBTable class
#DynamoDBTable(tableName = "receipt")
public class Receipt {
private Long id;
private List<ValidationFailure> validation;
#DynamoDBHashKey(attributeName = "id")
public Long getId() {
return id;
}
#DynamoDBTypeConverted(converter = ValidationConverter.class)
public List<ValidationFailure> getValidation() {
return validation;
}
public void setId(Long id) {
this.id = id;
}
public void setValidation(List<ValidationFailure> validation) {
this.validation = validation;
}
}
Convertor:
public class ValidationConverter implements DynamoDBTypeConverter<List<String>, List<ValidationFailure>> {
#Override
public List<String> convert(List<ValidationFailure> object) {
List<String> result = new ArrayList<String>();
if (object != null) {
object.stream().forEach(e -> result.add(e.name()));
}
return result;
}
#Override
public List<ValidationFailure> unconvert(List<String> object) {
List<ValidationFailure> result = new ArrayList<ValidationFailure>();
if (object != null) {
object.stream().forEach(e -> result.add(ValidationFailure.valueOf(e)));
}
return result;
}
}
It's working for me, I have used the Set
#DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.SS)
var roles: MutableSet<Employee.Role>? = null
I think the same approach would work for List with DynamoDBAttributeType.L
I found the answer myself. I create a custom marshall like below.
public class TransportMarshaller implements DynamoDBMarshaller<List<Transport>> {
#Override
public String marshall(List<Transport> transports) {
List<String>transportMap=new ArrayList<>();
for(Transport transport:transports){
transportMap.add(transport.name());
}
return transportMap.toString().replaceAll("\\[|\\]", "");//Save as comma separate value for the purpose of easiness to unmarshall
}
#Override
public List<Transport> unmarshall(Class<List<Transport>> aClass, String s) {
List<String>map= Arrays.asList(s.split("\\s*,\\s*")); //split from comma and parse to List
List<Transport>transports=new ArrayList<>();
for (String st:map){
transports.add(Transport.valueOf(st));
}
return transports;
}
}

Jackson Json with nested parametric classes

Listing:
import java.util.List;
public class Listing<T> {
List<Thing<T>> children;
public List<Thing<T>> getChildren() {
return children;
}
public void setChildren(List<Thing<T>> children) {
this.children = children;
}
}
Thing:
public class Thing<T> {
private String type;
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Link:
public class Link {
private String author;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
and here's an example of serialization and deserialization...
public static void main(String[] args) throws IOException {
Link link1 = new Link();
link1.setAuthor("JohnDoe");
Link link2 = new Link();
link2.setAuthor("MaryJane");
List<Thing<Link>> things = new ArrayList<Thing<Link>>();
Thing<Link> thing1 = new Thing();
thing1.setData(link1);
thing1.setType("t3");
Thing<Link> thing2 = new Thing();
thing2.setData(link2);
thing2.setType("t3");
things.add(thing1);
things.add(thing2);
Listing<Link> listing = new Listing<Link>();
listing.setChildren(things);
Thing<Listing> thing = new Thing<Listing>();
thing.setType("listing");
thing.setData(listing);
File jsonFile = new File("src/testMap.txt");
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(jsonFile, thing);
//String jsonString = "{\"type\":\"listing\",\"data\":{\"children\":[{\"type\":\"t3\",\"data\":{\"author\":\"JohnDoe\"}},{\"type\":\"t3\",\"data\":{\"author\":\"MaryJane\"}}]}}";
JavaType jsonType = mapper.getTypeFactory().constructParametricType(Thing.class, Listing.class);
Thing<Listing> readThing = mapper.readValue(jsonFile, jsonType);
}
The problem that I'm having is that the Things contained in the Listing in the sample code above are not parametrized with Link, so their data field is returned as an Object (which is actually LinkedHashMap).
I want to be able to do something like this:
List<Thing<Link>> readListingChildren = readThing.getData().getChildren();
String author = readListingChildren.get(0).getData().getAuthor();
My question is, how would I get this to work using Jackson json?
Note: there will be multiple different types of objects contained by Things, and a Thing's data member's type is defined (or should be defined) by the data object's "type" field, using strings such as t1, t2, t3, etc. which map to different classes.
To achieve a serialized String like
{
"data":{
"type":"listing",
"children":[
{
"data":{
"type":"t3",
"author":"JohnDoe"
}
},
{
"data":{
"type":"t3",
"author":"MaryJane"
}
}
]
}
}
and to use the type information to correctly deserialize the concrete class you may use
#JsonTypeName("listing")
public class Listing<T> {
List<Thing<T>> children;
public List<Thing<T>> getChildren() {
return children;
}
public void setChildren(final List<Thing<T>> children) {
this.children = children;
}
}
public class Thing<T> {
private T data;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(Link.class),
#JsonSubTypes.Type(Listing.class)
})
public T getData() {
return data;
}
public void setData(final T data) {
this.data = data;
}
}
#JsonTypeName("t3")
public class Link {
private String author;
public String getAuthor() {
return author;
}
public void setAuthor(final String author) {
this.author = author;
}
}

Categories