my backend must offer two different APIs - different access to the same models, respectively, same implementation and same mappings to the database. Models are send as JSONs and they are consumed by the backend in the same way.
But different JSON representations are necessary on each API.
F.e. I'd like to name some fields differently (w/ #JsonProperty f.e.) or want to omit some.
As mentioned, they should be consumed by the controllers in the same way they are produced.
Since only the representation differs: is there a simple and DRY compliant way to accomplish this?
Example to this:
Calling
ProductsController.java
sym/products/1
should return
{
"id": 1,
"title": "stuff",
"label": "junk"
}
and calling
ProductsController.java
frontend/products/1
should return
{
"id": 1,
"label": "junk",
"description": "oxmox",
"even-more": "text"
}
Thanks a lot!
Tim
Separate DTOs may be the best solution.
An alternate (assuming you are using Jackson) is to have one DTO with the all the different fields, and then use MixIns to control how the DTO is serialized.
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(SomeDTOWithLabel.class, IgnoreLabelMixin.class);
SomeDTOWithLabel dto = new SomeDTOWithLabel();
dto.setLabel("Hello World");
dto.setOtherProperty("Other property");
String json = objectMapper.writeValueAsString(dto);
System.out.println("json = " + json);
}
public static class SomeDTOWithLabel {
private String label;
private String otherProperty;
public String getOtherProperty() {
return otherProperty;
}
public void setOtherProperty(String otherProperty) {
this.otherProperty = otherProperty;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
public abstract class IgnoreLabelMixin {
#JsonIgnore
public abstract String getLabel();
}
}
For instance we have DTOs with deprecated properties that old clients may still depend on, but we don't want to send them to newer client, so we use MixIns to supress them.
If this is simply a case of returning a lightweight payload depending on which path you call, you can configure your json serializer (ObjectMapper) to omit empty fields. Then in your service only select and populate the subset of fields you wish to return.
objectMapper.setSerializationInclusion(Include.NON_NULL); // omits null fields
However, if you wish to return differently named fields, use a different API model.
Related
I have:
{
"id": "2021-04-03T15-SV_Waldhof_Mannheim--Zwickau",
"something": {
"id": "12",
"value": 1.5
}
}
I want get value: 1.15, and store it in my variable.
How can i do it with #JsonPropety?
#JsonProperty("something[value}") //how to do it correctly?
private float value;
How i parse JSON:
restTemplate.exchange(MY_GET_REQUEST, HttpMethod.GET, entity, new ParameterizedTypeReference<List<MyEntity>>(){})
I will be grateful for any help, if you know identical topics - just send link
UPDATED
something.value does not work
The same problem with unpacking, such as:
#JsonProperty("something")
public void setLng(Map<String, Float> coordinates) {
this.value= (Float.parseFloat(coordinates.get("value")));
}
Also does not work
You have 2 options:
Use custom deserializer for your response. In this case you able to populate any target DTO in any way. Here you could find example of custom deserializer
Use the same structure for your DTO as in response (with sub object) and add additional method in root DTO to access this value. But in this case it could produce side effects on serialization (for example, additional field in root DTO)
UPDATE
Such configuration is working for me
public static class Obj {
#JsonProperty("id")
String id;
Float value;
#JsonProperty("something")
public void value(Map<String, Object> obj) {
this.value = Float.parseFloat(obj.get("value").toString());
}
}
what I am trying to do is,
If I take one pojo class like
#Entity
#Table(name = "property_table")
public class Property {
#Id
#Column(name = "property_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int propertyId;
#Column(name = "property_name")
private String propertyName;
#Column(name = "property_type")
private String propertyType;
}
In RestController I wrote Two Methods like
#GetMapping(value = "/getProperties", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public List<Property> getProperties() {
//some code
}
#GetMapping(value = "/getPropertyById", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public Property getPropertyById() {
//some code
}
So, hear what I am trying to do is
for first api method I want return json like some parameters from Property pojo class i.e., like
for getProperties api method
{
"property":[
{
"propertyId":001,
"propertyName":"PROPERTY 1"
},
{
"propertyId":002,
"propertyName":"PROPERTY 2"
}
],
In the Above json I want to return only two parameters i.e propertyId,propertyName and remaining parameter i.e propertyType I dont want to retun in json.
How to return like that?
and for the second api method I want to return all three parameters. i.e., like below
for getPropertyById api method
{
"propertyId":001,
"propertyName":"PROPERTY 1",
"propertyType:"PROPERTY_TYPE 1"
},
how to maintain different json response using same pojo class with different parameters for different api methods.
please help me to solve this isuue.
Thanks.
REST API under/over-fetching is a well-known problem. There's only two (classical ways) to handle that.
The first one is to build one model per each attribute visibility state. So, in your case, you'll need to create two different models (this kind of models are called DTO - Data Transfert Object). One model will have a propertyType attribute, the other will not. The model Property you've shared shows that you use the same class as entity and as transfert object. This solution will add some complexity to your app because you will have to implement some mappers to convert your entity to a corresponding DTO.
The second one is to accept that you send an attribute that will not be useful (be aware of the over-fetching). This solution is often the most adopted one. The cons of this solution is when you don't want to send something to your client (imagine a User model, you want to get the password from your client but you don't want to sent it back to it). Another obvious negative point is that the transactions will be larger but it is negligible in most cases
I would strongly advice you to keep your #Entity isolated in the 'db' layer. So that changes on the database side don't affect your API and vice versa. Also, you will have much better control over what data is exposed in your API. For your needs you can create 2 true DTOs, like PropertyDto and PropertyDetailsDto (or using private fields and getters/setters).
public class PropertyDto {
public String propertyId;
public String propertyName;
}
public class PropertyDetailsDto extends PropertyDto {
public String propertyType;
}
Map your #Entity to a specific dto corresponding to your needs.
EDIT
public List<PropertyDto> getProperties() {
return toPropertyDtos(repository.findAll());
}
public PropertyDetailsDto getPropertyById(Long id) {
return toPropertyDetailsDto(repository.findBy(id));
}
in some Mapper.java
...
public static List<PropertyDto> toPropertyDtos(List<Property> properties) {
return properties.stream()
.map(Mapper::toPropertyDto)
.collect(toList());
}
private static PropertyDto toPropertyDto(Property property) {
PropertyDto dto = new PropertyDto();
dto.propertyId = property.propertyId;
dto.propertyName = property.propertyName;
return dto;
}
// same stuff for `toPropertyDetailsDto`, you could extract common mapping parts in a separate private method inside `Mapper`
...
I am creating a client for the following format of JSON -
{
"results": [
{
"Product": "K265113",
"Language": "EN",
"LongText": "FIXTURE,INTERIOR,WALL"
}
]
}
The JSON always contains "results" field which is an array of a single element (it will always be a single element in this array). I just need LongText field from the JSON and nothing else. I am using Spring RESTTemplate.
I know that it works if I create two DTOs like -
public class ParentDTO
{
private List<ChildDTO> results;
public List<ChildDTO> getResults()
{
return results;
}
public void setResults(List<ChildDTO> results)
{
this.results = results;
}
}
public class ChildDTO
{
private String longText;
public String getLongText()
{
return longText;
}
#JsonProperty("LongText")
public void setLongText(String longText)
{
this.longText = longText;
}
}
But is there any way to read longText by creating a single DTO as the parent DTO is not having any useful field as I know there will always but just one element in the results array.
The reason you need only single DTO could be that you want only single class to perform this task. You can achieve that using ChildDTO as inner class which will make it more readable and maintainable.
The other way is to not parse the spring template response into DTOs instead use JSONNode class of Jackson databind API.
JsonNode root = objectMapper.readTree(response.getBody());
You can find more information at
https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/JsonNode.html
You can traverse down the tree and could retrieve the value of the attribute directly without any DTOs.
Need to map multiple types of JSON responses to a single POJO so that I can compare the different objects to provide insight about the differences.
I had tried mapping the first response to the POJO and parsed the second response to populate the defined POJO:
class XXX {
#JsonProperty("accountHolder")
private String accountHolder;
#JsonProperty("routingNumber")
private String routingNumber;
#JsonProperty("balance")
private List<Balance> balance;
#JsonProperty("accountName")
private String accountName;
#JsonProperty("bankTransferCodeType")
private String bankTransferCodeType;
#JsonProperty("individualInformation")
private IndividualInformation individualInformation;
#JsonProperty("acctType")
private String acctType;
#JsonProperty("transactionList")
private TransactionList transactionList;
#JsonProperty("accountNumber")
private String accountNumber;
#JsonProperty("uniqueId")
private String uniqueId;
#JsonProperty("bankNetID")
private String bankNetID;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
}
First response:
[
{
"ACCOUNT_NAME": "",
"ACCOUNT_NUMBER": "",
"AVAILABLE_BALANCE": null,
"CURRENT_BALANCE": "",
"FULL_ACCOUNT_NUMBER": null,
}
]
Second response:
"bankAccount": [
{
"accountName": "",
"accountNumber": "",
"routingNumber": "",
"fullAccountNumber": "",
"bankTransferCodeType": "",
"acctType": "",
"transactionList": {
"transaction": [
{
"amount": {
"curCode": "",
"content": ""
}
],
"oldestTxnDate": ""
},
"uniqueId":
}
}
Expecting a generic way to map the different structured JSON entities to single POJO.
How to map multiple JSON responses to a single Java POJO?
As both responses seem to be completely different from each other, with nothing in common, I would refrain from attempting to use a single class for reading both responses.
Expecting a generic way to map the different structured JSONs to single POJO.
You could parse both responses as a Map<String, Object> and then map the values to a common class.
You could create separated classes for mapping each response. It will allow you to decouple them and evolve them as you need. You also can use use mapping frameworks such as MapStruct for reducing the boilerplate code when mapping from one object to another.
It doesn’t seems to have any generic way. But you can do this:
Create multiple domain classes for each response type
Create a single standard domain class
Create mapper for each response class to map that to standard domain
class. You can use MapStruct reference here
I would suggest using Jackson Json Views. Here is an example for the same :
Example
public class Views {
public class Global {
}
public class Internal extends Global {
}
}
class XXX {
#JsonView(Views.Global.class)
#JsonProperty("accountHolder")
private String accountHolder;
#JsonView(Views.Internal.class)
#JsonProperty("routingNumber")
private String routingNumber;
}
Hope it helps.
What I did is I created a MyResponse model containing basically all response fields from the JSON response you expect to get.
MyResponse has c-tor or receiving these fields or setters allowing setting them.
Then I created some kind of service class MyService that can issue multiple requests and gets responses back.
Then you just do something like this in some kind of manager class or whatever you call it:
MyService mySer = new MyService();
MyResponse myRes = new MyResponse(
mySer.getDetails(),
mySer.getPicture(),
mySer.getSomethingElse()
);
These calls (getDetails, getPicture...) send requests to end point and return responses which are then just mapped into the the fields of MyResponse class constructor. This happens by the framework so MyResponse has annotations #XmlRootElement and #XmlAccessorType of type FIELD to ensure that happens.
If for whatever reason, you dont want to create response containing result of getPicture for example, you just assign null to that imput parameter.
I suggest to use #JsonProperty("") and #JsonAlias("").
class XXX {
#JsonAlias("accountName")
#JsonProperty("ACCOUNT_NAME")
private String name;
#JsonAlias("routingNumber")
#JsonProperty("ROUTING_NUMBER")
private String routing;}
I hope it helps.
Using Jackson data binding, what's the neatest way to skip a bad chunk of data, without rejecting the whole parse?
Take these classes (I'm using public fields just to keep the code short):
public class ClassWhichCouldFailConstruction {
public ClassWhichCouldFailConstruction(String s) {
if(s.charAt(0) > 'L') {
throw new BadParameterException();
}
// else init code here.
}
}
public class User {
public String name;
public ClassWhichCouldFailConstruction failable;
}
public class AppInfo {
public List<User> users;
}
... and this code to parse it:
AppInfo appinfo = (List<User>) objectMapper.readValues(jsonStream, AppInfo.class);
... and this JSON:
{ "users": [
{ "name": "John", "failable": "Example" },
{ "name": "Jane", "failable": "No good" }
]
}
By default ClassWhichCouldFailConstruction("No good") will throw an exception which will bubble up to the caller of objectMapper.readValues().
How can I make it return a AppInfo object containing a users list that is one item long (the valid item)?
And can I run a routine to deal with (e.g. to log) the skipped entry?
I know I can achieve this with a custom deserializer:
public class User {
public String name;
#JsonDeserialize (using = MyCustomDeserializer.class)
public ClassWhichCouldFailConstruction failable;
}
... in which MyCustomDeserializer consumes the content in incremental mode. I'm looking for an option which takes advantage of data binding. Consider that ClassWhichCouldFailConstruction might be something a whole lot more complicated, so writing a custom parser would be laborious.
Use Bean Validation API instead of throwing exception from constructor -- aspects of JSON parsing and data-binding (that Jackson does) can be separated from validation logic. This is where Bean Validator helps: you can declaratively define rules and constraints.