Java show/hide object fields depending on REST call - java

Is it possible to config the Item class that depending on the REST call to show and hide specific fields?
For example I want to hide colorId (and show categoryId) from User class when calling XmlController and vice versa when calling JsonController.
Item class
#Getter
#Setter
#NoArgsConstructor
public class Item
{
private Long id;
private Long categoryId; // <-- Show field in XML REST call and hide in JSON REST call
private Long colorId; // <-- Show field in JSON REST call and hide in XML REST call
private Long groupId;
#JacksonXmlProperty(localName = "item")
#JacksonXmlElementWrapper(localName = "groupItems")
private List<GroupedItem> item;
}
JSON Controller
#RestController
#RequestMapping(
path = "/json/",
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class JsonController
{
#Autowired
private Service service;
#RequestMapping(path = "{colorId}")
public Item getArticles(#PathVariable("colorId") Long colorId)
{
return service.getByColor(colorId); // returns JSON without "categoryId"
}
}
XML Controller
#RestController
#RequestMapping(
path = "/xml/",
produces = MediaType.APPLICATION_XML_VALUE)
public class XmlController
{
#Autowired
private Service service;
#RequestMapping(path = "{categoryId}")
public Item getArticles(#PathVariable("categoryId") Long categoryId)
{
return service.getByCategory(categoryId); // returns XML without "colorId"
}
}

Yes, this is possible using Jackson JSON Views and the method ObjectMapper#writerWithView.
You just need to configure your ObjectMapper differently for both controllers and you're good to go
An example of a Jackson JSON View would be the following where we notice that ownerName is only accessible internally and not publically
public class Item {
#JsonView(Views.Public.class)
public int id;
#JsonView(Views.Public.class)
public String itemName;
#JsonView(Views.Internal.class)
public String ownerName;
}

I've solved it by creating following View:
public class View
{
public static class Parent
{
}
public static class Json extends Parent
{
}
public static class Xml extends Parent
{
}
}
With this config it is possible to set up the Class as:
#Getter
#Setter
#NoArgsConstructor
#JsonView(View.Parent.class)
public class Item
{
private Long id;
#JsonView(View.Xml.class) // <-- Show field in XML REST call and hide in JSON REST call
private Long categoryId;
#JsonView(View.Json.class) // <-- Show field in JSON REST call and hide in XML REST call
private Long colorId;
private Long groupId;
#JacksonXmlProperty(localName = "item")
#JacksonXmlElementWrapper(localName = "groupItems")
private List<GroupedItem> item;
}
(Note: if you want to list List<GroupedItem> item in your response you need to define #JsonView(View.Parent.class) in GroupedItem as well)
Finally, if you are using Spring, the REST requests (See question) can be defined as:
#JsonView(View.Json.class) // or #JsonView(View.Xml.class) in other case
#RequestMapping(path = "{colorId}")
public Item getArticles(#PathVariable("colorId") Long colorId)
{
return service.getByColor(colorId); // returns JSON without "categoryId"
}

Related

Deserialize two different JSON representations into one object

I have Java class like
#Data
public class Comment {
private Integer id; // should be used anyhow
private Long refId; // for internal purpose -> not be serialized
private String text; // should be used in QuickComment
private String patch; // should be included in PatchComment ONLY
private String status; // should be included in StatusComment ONLY
}
and I have
#Data
public class Response{
private Comment statusComment;
private Comment patchComment;
}
I thought about using JsonView like
public class Views{
public interface StatusComment{}
public interface PatchComment{}
}
and apply them to the inital class
#Data
public class Comment {
#JsonView({Views.StatusComment.class, Views.PatchComment.class})
private Integer id; // should be used anyhow
private Long refId; // for internal purpose -> not be serialized
#JsonView({Views.StatusComment.class, Views.PatchComment.class})
private String text; // should be used anyhow
#JsonView(Views.PatchComment.class)
private String patch; // should be included in PatchComment ONLY
#JsonView(Views.StatusComment.class)
private String status; // should be included in StatusComment ONLY
}
and the Response
#Data
public class Response{
#JsonView(Views.StatusComment.class)
private Comment statusComment;
#JsonView(Views.PatchComment.class)
private Comment patchComment;
}
But somehow it fails completely. It fails completly, ie. nothing is filtered. Is it problem with Lombok. Or is it defined incorrect?
How do you serialize your objects? Are you using Spring? Are you using the ObjectMapper directly?
If you're using Spring then what you need to do is annotate method of your controllers with #JsonView(Views.StatusComment.class) or #JsonView(Views.PatchComment.class) like:
For reading GET endpoints
#JsonView(Views.StatusComment.class)
#RequestMapping("/comments/{id}")
public Comment getStatusComments(#PathVariable int id) {
return statusService.getStatuscommentById(id);
}
For writing:
#RequestMapping(value = "/persons", consumes = APPLICATION_JSON_VALUE, method = RequestMethod.POST)
public Comment saveStatusComment(#JsonView(View.StatusComment.class) #RequestBody Comment c) {
return statusService.saveStatusComment(c);
}
If you're using the ObjectMapper directly, then what you need to do is specify the used View:
When writing:
ObjectMapper mapper = new ObjectMapper();
String result = mapper
.writerWithView(Views.StatusComment.class)
.writeValueAsString(comment);
When reading:
ObjectMapper mapper = new ObjectMapper();
Comment comment = mapper
.readerWithView(Views.StatusComment.class)
.forType(Comment.class)
.readValue(json);

Why are some of the variables in POJO equal to null after converting JSON RESTful Webservice?

I am consuming a RESTful webservice that returns a JSON payload. I can successfully consume the RESTful webservice and manage to populate some of the POJO attributes with JSON data. However, some other attributes are null when they are supposed to contain a value. How can I ensure that there are no more nulls?
I have defined 4 POJO classes. I have so far debugged by systematically by testing the variables for each class. This is using Springboot 2.2.0 and Jackson-databind.
The JSON schema I am trying to consume:
{
"items":[
{
"timestamp":"2019-09-18T16:42:54.203Z",
"carpark_data":[
{
"total_lots":"string",
"lot_type":"string",
"lots_available":"string"
}
]
}
]
}
For the above, I defined 4 classes:
public class Response {
#JsonProperty
private List<items> i;
#JsonIgnoreProperties(ignoreUnknown = true)
public class items {
private String timestamp;
private List<carpark_data> cpd;
#JsonIgnoreProperties(ignoreUnknown = true)
public class carpark_data {
private List<carpark_info> cpi;
private String carpark_number;
private String update_datetime;
#JsonIgnoreProperties(ignoreUnknown = true)
public class carpark_info {
private int total_lots;
private String lot_type;
private int lots_available;
When I run the below in Spring boot Main: I get null. Is my POJO modeling OK?
Response resp = restTemplate.getForObject("")
c = resp.getItems().get(0).getCarpark_data().get(0);
log.info("The last update time for the car park data = " +
c.getUpdateDatetime());
Your model does not fit to JSON payload. If we assume that JSON payload has a structure like below:
{
"items": [
{
"timestamp": "2019-09-18T16:42:54.203Z",
"carpark_data": [
{
"total_lots": "1000",
"lot_type": "string",
"lots_available": "800"
}
]
}
]
}
We can deserialise it as below:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
Response response = mapper.readValue(jsonFile, Response.class);
System.out.println(response.getItems().get(0).getData().get(0));
}
}
class Response {
private List<Item> items;
//getters, setters, toString
}
class Item {
private String timestamp;
#JsonProperty("carpark_data")
private List<CarParkInfo> data;
//getters, setters, toString
}
class CarParkInfo {
#JsonProperty("total_lots")
private int totalLots;
#JsonProperty("lot_type")
private String lotType;
#JsonProperty("lots_available")
private int lotsAvailable;
//getters, setters, toString
}
Above code prints:
CarParkInfo{totalLots=1000, lotType='string', lotsAvailable=800}
Hope you find the solution.
It is in POJO, you need to check the fieldName and object structure.
Seeing the Json above, your response model returns list of items and in each item you have list of carpark_data. So, basic modelling should be like this. And you can include respective setter and getter.
public class Response {
#JsonProperty
private List<items> items;
#JsonIgnoreProperties(ignoreUnknown = true)
public class items {
private String timestamp;
private List<carpark_data> carpark_data;
#JsonIgnoreProperties(ignoreUnknown = true)
public class carpark_data {
private int total_lots;
private String lot_type;
private int lots_available;
}
You need to have fields name in POJO class same in the Json response or you can set JsonProperty for that field. Like this
#JsonProperty("items")
private List<items> i;
#JsonProperty("carpark_data")
private List<carpark_data> cpd;

Create a Post method to save an object with multiples id

I have in my controller:
#RestController
public class OneTwoController {
private OnTwoService _service;
//... more code
#PostMapping("/api/one-two")
#CrossOrigin
public ResponseEntity<ServiceResponse> save(#RequestBody OneTwo model) {
return ResponseEntity.ok().body( _service.Save(model));
}
In my entity:
#Entity(name = "OneTwo")
#Where (clause = "deleted='false'")
public class OneTwo{
#EmbeddedId
private OneTwoKey_id;
public OneTwo(OneTwoKey id) {
this._id = id;
}
#JsonProperty("oneTwo")
public void setId(OneTwoKey value) {
this._id = value;
}
The OneTwoKey class:
public class OneTwoKey implements Serializable {
#Column(name = "OneID")
private int _oneID;
#Column(name = "TwoID")
private int _twoID;
public OneTwoKey(int oneID, int twoID) {
this._oneID = oneID;
this._twoID = twoID;
}
}
The json that I send to the Rest API:
{
"oneTwo": {
"oneID": 83,
"twoID": 69
},
"deleted": true
}
The issue is that both ids arrive null, so the service can't do the insert on the DB.
How can I deal with those cases when the ids are more than one?
Try adding setters in the OneTwoKey class to make it easier for the JSON deserializer:
#JsonProperty("oneID")
public void setOneID(int oneID) {
this._oneID = oneID;
}
#JsonProperty("twoID")
public void setTwoID(int twoID) {
this._twoID = twoID;
}
Another solution is to create a DTO, use it to receive the data in the controller and then convert it to your entity:
public class OneTwoDTO {
private Map<String, Int> oneTwo;
private boolean deleted;
// setters & getters
}
Simply what you can do is instead of using
public ResponseEntity<ServiceResponse> save(#RequestBody OneTwo model) {
you can use
public ResponseEntity<ServiceResponse> save(#RequestBody String model) {
Now convert the String to json and get all the key value pairs, it would be easier if you have dynamic number of variables and you want to capture them all.
or you can use tools like jsonschema2pojo whick take a json schema and generate a pojo. In the json schema if you set
"additionalProperties": true
you can capture all the values.
Could you make sure the problem is not because of case sensitivity?
Lower case the column names. Also could you use public access on those variables as well? These are my initial guesses as to why the payload is not being binded correctly.
public class OneTwoKey implements Serializable {
#Column(name = "oneID")
public int _oneID;
#Column(name = "twoID")
public int _twoID;

Missing name, in state: START_OBJECT parsing XML using Jackson

I'm trying to parse some XML that looks like this:
<correlationMatrix>
<assetMatrix numAssets="45">
<correlations asset="Name1" />
<correlations asset="Name2">
<correlation asset="Name3">1.23</correlation>
</correlations>
<correlations asset="Name4">
<correlation asset="Name5">2.34</correlation>
<correlation asset="Name6">3.45</correlation>
</correlations>
</assetMatrix>
</correlationMatrix>
I've created 3 classes:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CorrelationMatrix {
private List<Correlations> assetMatrix;
public List<Correlations> getAssetMatrix() {
return assetMatrix;
}
public void setAssetMatrix(List<Correlations> assetMatrix) {
this.assetMatrix = assetMatrix;
}
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
public class Correlations {
private String asset;
private List<Correlation> correlation;
public String getAsset() {
return asset;
}
public void setAsset(String asset) {
this.asset = asset;
}
public List<Correlation> getCorrelation() {
return correlation;
}
public void setCorrelations(List<Correlation> correlation) {
this.correlation = correlation;
}
}
Then finally
#JsonIgnoreProperties(ignoreUnknown = true)
public class Correlation {
}
As you can see I've removed everything from the final inner class, but it still fails to parse. I've tried removing <correlations asset="Name1" /> from the input but that's not the source of the problem. If I remove private List<Correlation> correlation; from Correlations then that does then parse successfully but obviously doesn't have the information I need.
What is it that I need to do differently here to parse what is essentially a 2 dimensional array from XML into Java using Jackson (2.2.0 if that matters)?
The error I get is:
Missing name, in state: START_OBJECT (through reference chain: CorrelationMatrix["assetMatrix"]->Correlations["correlation"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(
Update:
The problem seems to be associated with the values inside correlation. If I remove 1.23, 2.34 and 3.45 from my example data then it parses - so I need to somehow tell Jackson how to map them.
I was able to parse all the elements in the example xml with these modified classes (add getters, setters and use correct name setCorrelation in Correlations):
class CorrelationMatrix {
private AssetMatrix assetMatrix;
}
class AssetMatrix {
#JacksonXmlProperty(isAttribute = true)
private int numAssets;
#JacksonXmlElementWrapper(useWrapping = false)
private List<Correlations> correlations;
}
class Correlations {
#JacksonXmlProperty(isAttribute = true)
private String asset;
#JacksonXmlElementWrapper(useWrapping = false)
private List<Correlation> correlation;
}
class Correlation {
#JacksonXmlProperty(isAttribute = true)
private String asset;
#JacksonXmlText
private double correlation;
}
I didn't need #JsonIgnoreProperties(ignoreUnknown = true) anywhere
#JacksonXmlProperty(isAttribute = true) is needed for attributes like asset and numAssets
There are 2 types of lists in the xml that are both unwrapped so specify it with this #JacksonXmlElementWrapper(useWrapping = false)
You can parse the innermost double numbers with this #JacksonXmlText although the field in Java is not text.
I introduced a wrapper class AssetMatrix to capture numAssets

Spring HATEOAS with nested resources and JsonView filtering

I am trying to add HATEOAS links with Resource<>, while also filtering with #JsonView. However, I don't know how to add the links to nested objects.
In the project on on Github, I've expanded on this project (adding in the open pull request to make it work without nested resources), adding the "Character" entity which has a nested User.
When accessing the ~/characters/resource-filtered route, it is expected that the nested User "player" appear with the firstNm and bioDetails fields, and with Spring generated links to itself, but without the userId and lastNm fields.
I have the filtering working correctly, but I cannot find an example of nested resources which fits with the ResourceAssembler paradigm. It appears to be necessary to use a ResourceAssembler to make #JsonView work.
Any help reconciling these two concepts would be appreciated. If you can crack it entirely, consider sending me a pull request.
User.java
//package and imports
...
public class User implements Serializable {
#JsonView(UserView.Detail.class)
private Long userId;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String bioDetails;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String firstNm;
#JsonView({ UserView.Detail.class, CharacterView.Detail.class })
private String lastNm;
public User(Long userId, String firstNm, String lastNm) {
this.userId = userId;
this.firstNm = firstNm;
this.lastNm = lastNm;
}
public User(Long userId) {
this.userId = userId;
}
...
// getters and setters
...
}
CharacterModel.java
//package and imports
...
#Entity
public class CharacterModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(CharacterView.Summary.class)
private Long characterId;
#JsonView(CharacterView.Detail.class)
private String biography;
#JsonView(CharacterView.Summary.class)
private String name;
#JsonView(CharacterView.Summary.class)
private User player;
public CharacterModel(Long characterId, String name, String biography, User player) {
this.characterId = characterId;
this.name = name;
this.biography = biography;
this.player = player;
}
public CharacterModel(Long characterId) {
this.characterId = characterId;
}
...
// getters and setters
...
}
CharacterController.java
//package and imports
...
#RestController
#RequestMapping("/characters")
public class CharacterController {
#Autowired
private CharacterResourceAssembler characterResourceAssembler;
...
#JsonView(CharacterView.Summary.class)
#RequestMapping(value = "/resource-filtered", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Resource<CharacterModel> getFilteredCharacterWithResource() {
CharacterModel model = new CharacterModel(1L, "TEST NAME", "TEST BIOGRAPHY", new User(1L, "Fred", "Flintstone"));
return characterResourceAssembler.toResource(model);
}
...
}
CharacterResourceAssembler.java
//package and imports
...
#Component
public class CharacterResourceAssembler implements ResourceAssembler<CharacterModel, Resource<CharacterModel>>{
#Override
public Resource<CharacterModel> toResource(CharacterModel user) {
Resource<CharacterModel> resource = new Resource<CharacterModel>(user);
resource.add(linkTo(CharacterController.class).withSelfRel());
return resource;
}
}

Categories