Deserialize two different JSON representations into one object - java

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);

Related

How to deserialize the generic response using Spring WebClient

I have a problem during the deserialization of a response. Let's suppose I have this response from third party using webclient .
Response :
{
"name":"FirstName",
"type":"Steel",
"Fee":{
"id":"1234",
"name":"FeeFirstName"
},
"address":"2nd Street"
}
This is how my pojo classes looks like
public class Fee{} //generic OR empty class
public class Foo{
private String name;
private String type;
private Fee fee;
private String address;
}
My webclient get response code :
#Autowired
private WebClient fooWebClient;
public Foo getFoo()
{
try{
return fooWebClient.get()
.uri(uriBuilder -> uriBuilder.path("/foo/fee").build("123"))
.header(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Foo.class)
.block();
}catch(Exception e){throw new ApiClientException(e.getMessage());}
}
The above webclient getFoo() code is not giving me the full response, the Fee is coming blank stating "Class has no fields". Rest of the values are coming properly in response.
Fee needs to be empty as any other object can also come.
Please let me know how to deserialize the whole response.
You don't need the Fee class, you can get rid of it entirely and use a Map instead:
public class Foo {
private String name;
private String type;
private Map<String, Object> fee;
private String address;
}
We cannot dynamically create POJO and hence we are left with two options.
Add necessary fields to the 'Fee' class (If you know Fee structure upfront)
If you are not sure about the 'Fee' structure go for Map.
Because spring integrates Jackson you can create a custom Jackson JSON Deserializer for the Fee class that gives you more control:
#JsonDeserialize(using = FeeDeserializer.class)
public class Fee {
private String id;
private String name;
public Fee(String id, String name) {
this.id = id;
this.name = name;
}
}
import com.fasterxml.jackson.*;
public class FeeDeserializer extends JsonDeserializer<Fee> {
#Override
public Fee deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
JsonNode id = tree.get("id");
JsonNode name = tree.get("name");
return (id != null && name != null) ? new Fee(id.asText(), name.asText()) : null;
}
}
For more details see
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.json.jackson.custom-serializers-and-deserializers
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs-jackson

Reading a list in another object from a restTemplate response

I have gone through some of the questions here and their answers like this one which tells us how to extract a list of objects from a restTemplate response. It does not, however, solve my problem which is, I have an Entry class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Entry {
private String API;
private String Description;
private String Auth;
private boolean HTTPS;
private String Cors;
private String Link;
private String Category;
// getters and setters
}
then I have an Entry implementation class which has
#JsonIgnoreProperties(ignoreUnknown = true)
public class EntryImpl {
private Long count;
private ArrayList<Entry> entries;
public EntryImpl () {
}
// getters and setters
}
and here is my request implementation to consume this api
public class RestConsump {
public static void main (String [] args) {
RestTemplate restTemplate = new RestTemplate();
String url = "https://api.publicapis.org/entries";
EntryImpl entries = restTemplate.getForObject(url, EntryImpl.class);
System.out.println(entries.getEntries().get(0)); // returns null for all entries
System.out.println(entries.getCount()); // prints the numbers of entries
}
}
My question is, how do I implement it so that EntryImpl returns the list of entries and the count.
I executed the code above and got this response:
{API=AdoptAPet, Description=Resource to help get pets adopted, Auth=apiKey, HTTPS=true, Cors=yes, Link=https://www.adoptapet.com/public/apis/pet_list.html, Category=Animals}
1417
I used spring-web:5.3.8 for the RestTemplate, java version 15. Perhaps you are using an outdated RestTemplate?

Java show/hide object fields depending on REST call

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"
}

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;

How can I specify certain field to be serialized into JSON using Jackson?

I have two classes Athlete and Injury, the last one contains Athlete object, when the serialization happens I get the following JSON representation back:
{"id":X,"kindOfInjury":"...","muscle":"...","side":"...","outOfTrainig":Y,"injuryDate":"2018-Jun-02","athlete":{"id":X,"firstName":"...","lastName":"...","age":X,"email":"..."}}
I don't want to get all the information about Athlete - just an id value, like "athleteId":1, instead of getting the entire object representation.
So, I have found that I need to apply my custom Serializer which implements StdSerializer on Injury class. So this is what I got so far:
class InjurySerializer extends StdSerializer<Injury> {
public InjurySerializer() {
this(null);
}
public InjurySerializer(Class<Injury> i) {
super(i);
}
#Override
public void serialize(
Injury value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeStringField("kindOfInjury", value.getKindOfInjury());
jgen.writeStringField("muscle", value.getMuscle());
jgen.writeStringField("side", value.getSide());
jgen.writeNumberField("outOfTraining", value.getOutOfTraining());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MMM-dd");
Date date = new Date();
String ourformat = formatter.format(date.getTime());
jgen.writeStringField("injuryDate", ourformat);
jgen.writeNumberField("athleteId", value.getAthlete().getId());
jgen.writeEndObject();
}
}
And the actual Injury class:
#Entity
#Table(name = "INJURY")
#JsonSerialize(using = InjurySerializer.class)
public class Injury {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "INJURY_ID")
private Long id;
#Column(name = "KIND_OF_INJURY")
private String kindOfInjury;
#Column(name = "MUSCLE")
private String muscle;
#Column(name = "SIDE")
private String side;
#Column(name = "OUT_OF_TRAINING")
private Integer outOfTraining;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MMM-dd")
#Column(name = "INJURY_DATE")
private Date injuryDate;
#ManyToOne
#JoinColumn(name = "ATHLETE_ID")
private Athlete athlete;
So, this solution works, but it looks terrible...
Question is the following:
1) Is there any mechanism which provides me functionality to change the serialization of only ONE property which I really need, instead of writing all this tedious code, where the actual change is only in this line? :
jgen.writeNumberField("athleteId", value.getAthlete().getId());
2) Could you recommend me something to read about Jackson because at this point I have a little bit mess in my head about it?
Thanks for the patience and I'm looking forwards for your responses :)
You can use the Data Transfer Object (DTO) for that purposes.
Create a simple POJO like this:
public class InjuryDTO {
//all other required fields from Injury model...
#JsonProperty("athlete_id")
private Long athleteId;
}
And converter for it:
#Component
public class InjuryToDTOConverter{
public InjuryDTO convert(Injury source){
InjuryDTO target = new InjuryDTO();
BeanUtils.copyProperties(source, target); //it will copy fields with the same names
target.setAthleteId(source.getAthlete().getId());
return target;
}
}
You can use it like that:
#RestController("/injuries")
public class InjuryController {
#Autowired
private InjuryToDTOConverter converter;
#Autowired
private InjuryService injuryService;
#GetMapping
public InjuryDTO getInjury(){
Injury injury = injuryService.getInjury();
return converter.convert(injury);
}
}
The benefit of this approach is that you can have multiple DTOs for different purposes.
You might find it less tedious to use the #JsonIgnore annotation instead of writing a custom serializer. Take this example
public class Person {
private int id;
#JsonIgnore
private String first;
#JsonIgnore
private String last;
#JsonIgnore
private int age;
// getters and setters omitted
}
When Jackson serializes this class, it only includes the "id" property in the resulting JSON.
#Test
void serialize_only_includes_id() throws JsonProcessingException {
final var person = new Person();
person.setId(1);
person.setFirst("John");
person.setLast("Smith");
person.setAge(22);
final var mapper = new ObjectMapper();
final var json = mapper.writeValueAsString(person);
assertEquals("{\"id\":1}", json);
}
You can try manupulating json string using basic string replace method.
I ran your json and converted it to your desired format:
public static void main(String args[]) {
String json = "{\"id\":123,\"kindOfInjury\":\"...\",\"muscle\":\"...\",\"side\":\"...\",\"outOfTrainig\":Y,\"injuryDate\":\"2018-Jun-02\",\"athlete\":{\"id\":456,\"firstName\":\"...\",\"lastName\":\"...\",\"age\":14,\"email\":\"...\"}}";
JsonObject injury = new JsonParser().parse(json).getAsJsonObject();
JsonObject athelete = new JsonParser().parse(injury.get("athlete").toString()).getAsJsonObject();
String updateJson = injury.toString().replace(injury.get("athlete").toString(), athelete.get("id").toString());
updateJson = updateJson.replace("athlete", "athleteId");
System.out.println(updateJson);
}
output:
{"id":123,"kindOfInjury":"...","muscle":"...","side":"...","outOfTrainig":"Y","injuryDate":"2018-Jun-02","athleteId":456}
Dependency:
implementation 'com.google.code.gson:gson:2.8.5'
If you can replace with regex that will be bit more cleaner.

Categories