How can I generalize the Outer Class in Json Response mapping using REST template? - spring-boot

I'm developing a series of clients to webservices and I'm observing that they follow a simple json structure, like this:
{
"typex": { "property1" : "value"},
"page": 1,
"count": 200,
"next_page": 2
}
I have near 15 webservices each returning the same structure, where the main information is within the object "typex" and their properties.
Today, these returns needs to have, at least, 2 classes, one for outer data and one for inner information.
public class TypeXWrapper {
#JsonProperty
TypeX typex;
#JsonProperty
Integer page;
#JsonProperty
Integer count;
#JsonProperty
Integer next_page;
}
public class TypeX {
#JsonProperty
String property1;
}
In this way, it would be necessary to create 30 classes.
Is there any way to implement some kind of generalization for situations like this?
I was thinking something like:
public class GenericWrapper<InnerClass> {
#JsonProperty
InnerClass data;
#JsonProperty
Integer page;
#JsonProperty
Integer count;
#JsonProperty
Integer next_page;
}
public class TypeX {
#JsonProperty
String property1;
}
But the problem is that the root property of the inner class data, changes for each webservice endpoint. That means that the next answer, for example TypeY, the property will have the name "typey", like:
{
"typey": { "property2" : "value"},
"page": 1,
"count": 200,
"next_page": 2
}
Is there anything that I could use to achieve this generalization? The environment and frameworks are Spring Boot 2.1.18 (can change if needed), using resttemplate with the return object encapsulated in ParameterizedTypeReference.
Thanks!

Related

How to show data to user with DTO

This is the data that I am currently showing to the user:
{
"id": 3,
"name": "AB:11",
"description": "AB:11 is an Imperial Black Barley Wine brewed with ginger, black raspberries and chipotle peppers. A 12.8% rollercoaster of ginger zestiness and chipotle smokiness, all bound together with dark berry tartness and the decadent residual body of a Black Barley Wine.",
"method": {
"mash_temp": [
{
"temp": {
"value": 68,
"unit": "celsius"
}
}
]
}
And I don't need this "method" field. I tried to show data with DTO which looks like this:
public class Beer {
private Integer id;
private String name;
private String description;
private Method method;
private List<MashTemp> mashTemp;
private Temp temp;
// getters & setters
My DTO is giving me back a response like a:
"id": 1,
"name": "Bitch Please (w/ 3 Floyds)",
"description": "This limited edition American Barley Wine was brewed in collaboration with 3 Floyds Brewery. This beer had all the warm, boozy and smoky aspects of an Islay Scotch whisky with the sweet malt and devastatingly bitter attributes of a barley wine. Peat smoke features prominently, backed up with a complex fruity hop profile.",
"method": {
"mash_temp": [
{
"temp": {
"value": 65
}
}
]
},
"mashTemp": null,
"temp": null
Any Idea how to fix this?
You need the following classes:
public class BeerDto {
private Integer id;
private String name;
private String description;
private List<MashTempDto> mashTemp;
}
public class MashTempDto {
private TempDto temp;
}
public class TempDto {
private Integer value;
}
Now you need to map your original Beer object to BeerDto (the same is true for MashTempDto and TempDto) which you will then make available in your API.
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Beer.class)
public interface BeerDto {
#IdMapping
Integer getId();
String getName();
String getDescription();
Set<MashTempDto> getMashTemp();
#EntityView(MashTemp.class)
interface MashTempDto {
TempDto getTemp();
}
#EntityView(Temp.class)
interface TempDto {
Integer getValue();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
BeerDto a = entityViewManager.find(entityManager, BeerDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<BeerDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Custom Mapping from JSON to Java/POJO in Spring RestTemplate response

I have a Spring boot application where I am using RestTemplate to call a Rest API and I receive following JSON formatted response:
{
"data": [
{
"id": "1",
"type": "type1",
"config": {
"property1" : "value1",
"property2" : "value2"
}
},
{
"id": "2",
"type": "type2",
"config": {
"property3" : "value3",
"property4" : "value4",
"propArray": [ "element1", "element2"]
}
}
]
}
The individual elements within array 'data' has few different structures (2 examples above) where I would like to map different Class Types with individual elements which depends on the value of the element 'type'.
For example value 'type1' should map to an object of Class type 'Type1' and so on.
I have Classes created as below:
MyResponse:
public Class MyResponse {
List<Data> data;
..
\\getter and setters
}
Data:
public Interface Data {}
Type1:
public Class Type1 implements Data {
private String property1;
private String property2;
..
\\getter and setters
}
Type2:
public Class Type1 implements Data {
private String property3;
private String property4;
private List<String> propArray;
..
\\getter and setters
}
How can I map above conditional structure?
Only way I could think of it is get the returned value a String, convert it to JSONObject and process it to create your instances of your classes. For example,
String response = restTemplate.<your-uri>
JSONObject jsonObject = new JSONObject(response);
if (jsonObject.get(type).equals(type1) {
Type1 type1 = new Type1();
// set values
} else if (jsonObject.get(type).equals(type2) {
Type2 type2 = new Type2()
// set values
}
However, this is not scalable and if you are going add more and more types it would be very difficult to maintain a clean code.
Another way you can do this is to create a General Class and receive the response as List of that class. In this way Spring-boot/ jackson cand do the mapping. Again you have to add code to create the other classes from this general class. As Sam pointed out in the comment, this would be a preferred one since Jackson is faster than JSONObject. Here is sample class would look like,
class Response {
private Integer id;
private String type;
private Map<String, Object> config;
}
You still have to check the type and map to corresponding class.
Instead of writing such messy code, I would consider if I could re-architect your design/ response sent if you have control over it.

How to do not send #IdClass object in Spring JSON queries

I'm setting a server to get a CRUD api from a postgresql Database using JPA. Everytime I want to expose an object from the DB it duplicate the idObject.
When I get an object from the database using springframework and send it after that, it duplicate the idObject like this:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1",
"siteIdObject": {
"siteId": 3,
"contractId": "1"
}
}
SiteId and contractId are repeating...
but I want something like that:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1"
}
I want to avoid using DTO because I think there is a better way but I don't find it. Since I'm using springFramework for just one or two month I'm maybe forgeting something...
there is the code:
Site code:
#Entity
#IdClass(SiteId.class)
#Table(name = "site", schema="public")
public class Site {
#Id
#Column(name="siteid")
private Integer siteId;
#Id
#Column(name="clientid")
private Integer contractId;
private String name;
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy = "site")
public Set<Device> devices;
//setter, getter, hash, equals, tostring, constructor empty one and full one
SiteId code:
public class SiteId implements Serializable {
private Integer siteId;
private Integer contractId;
// setter, getter, constructor empty and full, hash and equals
Thanks to help :)
Bessaix Daniel
If you are using Spring you might also be using Jackson so if you annotate your SiteIdclass with #JsonIgnoreType it shouldn't be serialized at all when the Site object is serialized.
I am however unsure if this will break your application logic now that the id object is not serialized anymore.

spring-data-redis, empty list attribute value becomes null

I'm in the process of porting some microservices from SpringBoot1.5 to 2.1.
We are using spring-data-redis. it seems the default internal moves from jedis to lettuce.
The thing is we now observe some weird behaviours, when we save an object and then retrieve it, there is a tiny difference:
empty list attributes are replaced with null.
Here is an example:
//repo
public interface TestRepository extends CrudRepository<Test, String> {}
...
//object
#RedisHash(timeToLive = 60)
public static class Test{
#Id private String id;
int age;
List<String> friends;
}
...
//saving then retreiving
Test test = new Test("1", 15, Collections.emptyList());
System.out.println(test);
testRepository.save(test);
Test testGet = testRepository.findById("1").get();
System.out.println(testGet);
and here is what happens:
//before
{
"id": "1",
"age": 15,
"friends": []
}
//after
{
"id": "1",
"age": 15
}
the friends empty list has disappeared. This new behaviour affects our code in many places leading to NullPointerExceptions etc.
Apparently, there are multiple serializers available but this doesn't seem to have any effect. Any idea?
https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:serializer
for reference:
springBootVersion = '2.1.5.RELEASE'
springCloudVersion = 'Greenwich.SR1'
I met this problem too. I solved it like this:
#RedisHash(timeToLive = 60)
public class MyData implements Serializable {
#Id
private String id;
private List<Object> objects = new ArrayList<>();
}
If i will save MyData with empty list objects, when i pull it from Redis, objects in it will not be null and will be empty list. If i will save 'MyData' with not empty objects, objects not will be lost after deserialization.

Id field handling in Spring Data Mongo for child objects

I have been working in Spring Boot with the Spring Data MongoDB project and I am seeing behavior I am not clear on. I understand that the id field will go to _id in the Mongo repository per http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping.conventions.id-field. My problem is that it also seems to be happening for child entities which does not seem correct.
For example I have these classes (leaving out setters and getters for brevity) :
public class MessageBuild {
#Id
private String id;
private String name;
private TopLevelMessage.MessageType messageType;
private TopLevelMessage message;
}
public interface TopLevelMessage {
public enum MessageType {
MapData
}
}
public class MapData implements TopLevelMessage {
private String layerType;
private Vector<Intersection> intersections;
private Vector<RoadSegment> roadSegments;
}
public class RoadSegment {
private int id;
private String name;
private Double laneWidth;
}
and I create an object graph using this I use the appropriate MongoRepository class to save I end up with an example document like this (with _class left out):
{
"_id" : ObjectId("57c0c05568a6c4941830a626"),
"_class" : "com.etranssystems.coreobjects.persistable.MessageBuild",
"name" : "TestMessage",
"messageType" : "MapData",
"message" : {
"layerType" : "IntersectionData",
"roadSegments" : [
{
"_id" : 2001,
"name" : "Road Segment 1",
"laneWidth" : 3.3
}
]
}
}
In this case a child object with a field named id has its mapping converted to _id in the MongoDB repository. Not the end of the world although not expected. The biggest problem is now that this is exposed by REST MVC the _id fields are not returned from a query. I have tried to set the exposeIdsFor in my RepositoryRestConfigurerAdapter for this class and it exposes the id for the top level document but not the child ones.
So circling around the 2 questions/issues I have are:
Why are child object fields mapped to _id? My understanding is that this should only happen on the top level since things underneath are not really documents in their own right.
Shouldn't the configuration to expose id fields work for child objects in a document if it is mapping the field names?
Am I wrong to think that RoadSegment does not contain a getId() ? From Spring's documentation:
A property or field without an annotation but named id will be mapped
to the _id field.
I believe Spring Data does this even to nested classes, when it finds an id field. You may either add a getId(), so that the field is named id or annotate it with #Field:
public class RoadSegment {
#Field("id")
private int id;
private String name;
private Double laneWidth;
}
I agree this automatic conversion of id/_id should only be done at the top level in my opinion.
However, the way Spring Data Mongo conversion is coded, all java ojects go through the exact same code to be converted into json (both top and nested objects):
public class MappingMongoConverter {
...
protected void writeInternal(Object obj, final DBObject dbo, MongoPersistentEntity<?> entity) {
...
if (!dbo.containsField("_id") && null != idProperty) {
try {
Object id = accessor.getProperty(idProperty);
dbo.put("_id", idMapper.convertId(id));
} catch (ConversionException ignored) {}
}
...
if (!conversions.isSimpleType(propertyObj.getClass())) {
// The following line recursively calls writeInternal with the nested object
writePropertyInternal(propertyObj, dbo, prop);
} else {
writeSimpleInternal(propertyObj, dbo, prop);
}
}
writeInternal is called on the top level object, and then recalled recursively for each subobjects (aka SimpleTypes). So they both go through the same logic of adding _id.
Perhaps this is how we should read Spring's documentation:
Mongo's restrictions on Mongo Documents:
MongoDB requires that you have an _id field for all documents. If you
don’t provide one the driver will assign a ObjectId with a generated
value.
Spring Data's restrictions on java classes:
If no field or property specified above is present in the Java class
then an implicit _id file will be generated by the driver but not
mapped to a property or field of the Java class.

Resources