GSON deserialization behaviour - gson

I am deserializing a JSON payload to a POJO and in the POJO I have a HashMap (userIdMap) that is declared & initialized as follows:
public class ObjectProvider {
private String companyId;
#Expose(serialize = true, deserialize = true)
#SerializedName("entity_id")
private String entityId;
#Expose(serialize = true, deserialize = true)
#SerializedName("url")
private String url;
private String responseTemplate;
private Map<String, String> userIdMap = new HashMap<String, String>();
public String getEntityId() {
return entityId;
}
...// REST OF CODE REMOVED FOR READABILITY
}
However after the POJO is created when I try to access the userIdMap attribute to perform a GET operation I get java.lang.NullPointerException. Any ideas as to what could be going wrong ?

The issue has been solved. Looks like the default constructor was missing in the class. Once I add the default constructor and initialize the Map it works fine.

Related

Why methods doesn't see values set in constructor? (Spring Boot)

I have a class AuthorizationService like this:
#Service
public class AuthorizationService {
private final String code;
private final String client_id;
private final String redirect_uri;
private final String scope;
private final String show_dialog;
#Autowired
public AuthorizationService(
#Value("${spotify-property.response_type}") String code,
#Value("${spotify-property.client_id}") String client_id,
#Value("${spotify-property.redirect_uri}") String redirect_uri,
#Value("${spotify-property.scope}") String scope,
#Value("${spotify-property.redirect_uri}") String show_dialog) {
this.code = code;
this.client_id = client_id;
this.redirect_uri = redirect_uri;
this.scope = scope;
this.show_dialog = show_dialog;
System.out.println(code); //works
}
public ResponseEntity<HttpHeaders> getSpotifyRedirectionCode() {
HttpHeaders headers = new HttpHeaders();
System.out.println(client_id); //doesn't work
System.out.println(this.code); //does not work either
System.out.println(redirect_uri);
System.out.println(scope);
System.out.println(show_dialog);
//more code doesn't matter
return new ResponseEntity<>(headers, HttpStatus.TEMPORARY_REDIRECT);
}
Why these variables are ok in constructor but in methods are null, how to set them in correct way? Also
#Value("${spotify-property.response_type}")
private final String code;
does not work either.
Your class fields are final so get instantiated without your configuration values.
You can either use the constructor as you have before, or remove the final from your fields.
I'm not sure if my answer is 100% correct. But I believe that you cannot change final variables. The right way would be to remove the final keyword and keep the #Value annotations on the fields and the you don't have to use it in the constructor.
#Service
#RequiredArgsConstructor
public class AuthorizationService {
#Value("${spotify-property.response_type}")
private String code;
#Value("${spotify-property.client_id}")
private String client_id;
#Value("${spotify-property.redirect_uri}")
private String redirect_uri;
#Value("${spotify-property.scope}")
private String scope;
#Value("${spotify-property.redirect_uri}")
private String show_dialog;
public ResponseEntity<HttpHeaders> getSpotifyRedirectionCode() {
HttpHeaders headers = new HttpHeaders();
System.out.println(client_id); //works
System.out.println(this.code); //works too
System.out.println(this.redirect_uri);
System.out.println(this.scope);
System.out.println(this.show_dialog);
//more code doesn't matter
return new ResponseEntity<>(headers, HttpStatus.TEMPORARY_REDIRECT);
} }

Spring/Jackson AutoMapping for field that can be multiple types

In my application, I am trying to get an OAuth token from multiple different providers.
According to the OAuth Spec, scopes should be sent as a string that contains a space delimited list of scopes.
However, I have noticed that some implementations return the scope as a List of strings.
In my application, I would like to store the scopes as a string that contains a space delimited list of scopes, as per the specification.
To do so, I created an Dto that looks as such (this dto is the same as the entity):
public class OAuthTokenDto {
// variables named to reflect OAuth Spec sends them to us
private String access_token;
private String refresh_token;
private Integer expires_in;
private String scope;
private String token_type;
public OAuthTokenDto() {
}
...
}
Is there a way to handle the fact that scopes might be sent as a List? In its current state, I am using rest template to do the mapping and it throws an exception because of this mismatch
You can make use of Jackson's #JsonAnyGetter and #JsonAnySetter. What you can do is, you can catch the unspecified tokens with this in which case your scope element and then typecast depending on the object type.
public class OAuthTokenDto {
// variables named to reflect OAuth Spec sends them to us
private String access_token;
private String refresh_token;
private Integer expires_in;
private String token_type;
#JsonIgnore
private Map<String, Object> properties = new HaspMap<String, Object>();
public OAuthTokenDto() {
}
#JsonAnyGetter
public Map<String, Object> getProperties() {
return this.properties;
}
#JsonAnySetter
public void setProperty(String name, Object value) {
this.properties.put(name, value);
}
}
And you can verify if your scope is an object or list of objects as follows
if(properites.get("scope") instanceof List<String>)
{
List<String> scopeList = properties.get("scope");
}
else
{
String scope = properties.get("scope");
}
Thanks to #Deadpool, the following implementation works
From jackson 2.6 you can use JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY
#JsonProperty("scope")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private String[] scope;
Clean solution:
Create converter class:
class ScopeConverter extends StdConverter<List<String>, String> {
#Override
public String convert(List<String> scopes) {
return scopes.stream().collect(Collectors.joining(" "));
}
}
Use it for scope property:
#JsonDeserialize(converter = ScopeConverter.class)
public String scope;
Enable:
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Now you can deserialize both cases scope being List and one element (with space separated scopes)

Spring #RequestMapping method without #RequestBody

In a Spring Boot Controller method, how do I get the body of a POST? All of the examples I have seen use #RequestBody. How do I get the body without using #RequestBody?
I am writing a method to handle Slack Events. When Slack POSTs an event, the body is in JSON and often contains a "user" key. Depending on the type of event, the value of "user" can either be a string or an object. Because of this, I cannot create a single Class and write
#RequestMapping(path = "/slackRequest", method = RequestMethod.POST)
public String handleSlackRequest(#RequestBody final SlackRequest slackRequest)
Answer: Implementing the approach suggested by #ChiDov, the solution is to keep the #RequestBody, import
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
define the user field (and a new field to store the 'user' if it is a simple String value) as
#OneToOne
private SlackEventUser user;
private String slackUserId;
and define its Setter method as
#JsonSetter("user")
public void setUser(JsonNode userNode) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if (userNode.isObject()) {
SlackEventUser slackEventUser = mapper.convertValue(userNode, SlackEventUser.class);
this.user = slackEventUser;
} else {
String userString = mapper.convertValue(userNode, String.class);
this.slackUserId = userString;
this.user = null;
}
}
Updated: I would make your DTO like :
Class SlackRequest{
...
private String eventType;
private JsonNode user;
...
public String getUser(){
return user.asText();
}
}
and in controller:
#RequestMapping(path = "/slackRequest", method = RequestMethod.POST)
public String handleSlackRequest(#RequestBody final SlackRequest slackRequest){
if(slackRequest.getEventType == "objectEvent"){
SomeObject user = mapper.convertValue(slackRequest.getUser(), SomeObject.class);
// do something with the object
}else{
// do something with the user string
}
}
Get Inspiration from : How to deserialize dynamic field using Jackson?

Spring Boot request body semi-required fields

In our application user can write a message based on user id or screen name.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = ???) Long userId,
#JsonProperty(value = "screen_name", required = ???) String screenName,
#JsonProperty(value = "text", required = true) String text) {
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}
Fields userId and screenName can't be optional at same time, one should be provided.
How in Spring Boot to mark that they are semi-required?
This seems like more of a validation concern rather than deserialization.
Create a Validator then put #Valid within the #RequestMapping on the controller.
See more here:
Spring REST Validation Example
From jenkov tutorials:
#JsonValue
The Jackson annotation #JsonValue tells Jackson that Jackson should
not attempt to serialize the object itself, but rather call a method
on the object which serializes the object to a JSON string. Note that
Jackson will escape any quotation marks inside the String returned by
the custom serialization, so you cannot return e.g. a full JSON
object. For that you should use #JsonRawValue instead (see previous
section).
The #JsonValue annotation is added to the method that Jackson is to
call to serialize the object into a JSON string. Here is an example
showing how to use the #JsonValue annotation:
public class PersonValue {
public long personId = 0;
public String name = null;
#JsonValue
public String toJson(){
return this.personId + "," + this.name;
}
}
The output you would get from asking Jackson to serialize a
PersonValue object is this:
"0,null"
So you can use #JsonValue and put your code either to ignore or not from some fields when you try to convert into JSON
#JsonValue
public String toJson(){
//ignore fields or include them here
}
Just throw an IllegalArgumentException. The best case would be to deserialize, then run through a validator though so you separate the concerns of serialization, and domain validation.
class Message {
public final Long userId;
public final String screenName;
public final String text;
#JsonCreator
public Message(#JsonProperty(value = "user_id", required = false) Long userId,
#JsonProperty(value = "screen_name", required = false) String screenName,
#JsonProperty(value = "text", required = true) String text) {
if(userId == null && screenName == null) {
throw new IllegalArgumentException("userId or screenName must be provided.");
}
this.userId = userId;
this.screenName = screenName;
this.text = text;
}
}

How to send Java collections containing subclasses to spring controller

I'm trying to send collections to my spring MVC controller:
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#ModelAttribute("mapperList") MapperList mapperList) {
//prints {"users":null}
System.out.println(new ObjectMapper().writeValueAsString(mapperList));
return new ResponseDTO();
}
this is the code posting my users :
public ResponseDTO postUsers(ArrayList<User> users) {
ResponseDTO serverResponse = null;
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestMethod("POST");
// prints {"users":[{"property1":"x","property1":y}]}
System.out.println(objectMapper.writeValueAsString(new MapperList(users)));
objectMapper.writeValue(connection.getOutputStream(), objectMapper.writeValueAsString(new MapperList(users)));
//blabla ...
}
and this is the object containing my list :
public class MapperList implements Serializable {
private static final long serialVersionUID = 8561295813487706798L;
private ArrayList<User> users;
public MapperList() {}
public MapperList(ArrayList<User> users) {
this.setUsers(users);
}
public ArrayList<User> getUsers() {
return users;
}
public void setUsers(ArrayList<User> users) {
this.users = users;
}
}
and this is the users type to post:
public abstract class User implements Serializable {
private static final long serialVersionUID = -1811485256250922102L;
private String property1;
private String property2;
public User() {}
public User(String prop1, String prop2) {
// set properties
}
// getters and setters
}
the problem is, when I output the value of the users's array before to post it to the controller, I got the following json value :
{"users":[{"property1":"x","property1":y}]}
but in the controller, when I print what I get from the request body, I only get :
{"users":null}
I also tryed with the annotation #RequestBody instead of #ModelAttribute("mapperList") and a JSONException is displayed :
*A JSONObject text must begin with '{' at 1 [character 2 line 1]\r\n*
My array list of users contains only one user that should be displayed. I don't understand why this doesn't work...
Thanks for any help !
You can chnage your MapperList class definition as public class MapperList extends ArrayList<User>{ ..} you dont need to define any instance variable like private ArrayList users inside MapperList class. Use #Requestbody annotation. You will be able to use MapperList as a ArrayList
Try to use:
public class MapperList{
private List<User> users;
//setter and getter
//toString
}
public class User{
private String property1;
private String property2;
//getter + setter
}
json:
{"users":[{"property1":"x", "property2":"y"}]}
in controller use #RequestBody. In that case Jackson will map your json to ArrayList of users.
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList users) {
System.out.println(users);
return null;
}
no need to get objectMapper in that case. Don't forget to set content-type in request header to application/json. It required by Spring to handle #RequestBody processing.
If not working try to change MapperList:
List<User> users = new ArrayList<User>();
On the server side keep the #RequestBody annotation:
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList mapperList)
...
But this line causes problems:
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
First it converts the object to JSON and then again uses objectMapper to JSON-encode the string into output stream. Try the following instead:
connection.getOutputStream().write(
objectMapper.writeValueAsString(new MapperList(users))
.getBytes("UTF-8")
);
or directly output to stream:
objectMapper.writeValue(
connection.getOutputStream(),
new MapperList(users))
);
Zbynek gave me part of the answer. Indeed
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
doesn't work properly in my case
But moreover, my User class was an abstract class, with many type of User as subclasses. so the #RequestBody annotation couldn't work without specified the object type in the Json.
I used the following annotations on User class to make it working :
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SubClassA.class, name = "a"),
#JsonSubTypes.Type(value = SubClassB.class, name = "b")
})
Thanks a lot for all your answers.

Resources