I have a class called Channel which will have roles property as follows
public class Channel{
private int id;
private String roles;
}
And my JSON from client would be
{
"id":"12345787654323468",
"roles":[
{"name":"admin","permissions":["can publish","can reject"]},
{"name":"moderator","permissions":["can publish","can reject"]}
]
}
But when I convert this JSON to Channel object I am getting following exception
com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_ARRAY token
at [Source: java.io.StringReader#6d25f91; line: 1, column: 253] (through reference chain: com.pokuri.entity.Channel["roles"])
Now I want to deserialize this as a string into property roles of Channel class. Also can I write single custom deserializer to handle property of JSON array in any bean.
A custom Deserializer can do the trick here. :
class CustomDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.readValueAsTree();
return node.toString();
}
}
now to use this in your bean, you have to include it on roles field :
class Channel {
private long id;
#JsonDeserialize(using = CustomDeserializer.class)
private String roles;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
}
Note : I have taken value of id as long as it was showing error for int, as value is too large in id attribute.
Now ObjectMapper can easily deserialize your JSON to Channel class :
String json = "{\"id\":\"12345787654323468\",\"roles\":[{\"name\":\"admin\",\"permissions\":[\"can publish\",\"can reject\"]},{\"name\":\"moderator\",\"permissions\":[\"can publish\",\"can reject\"]}]}";
ObjectMapper mapper = new ObjectMapper();
Channel channel = mapper.readValue(json, Channel.class);
System.out.println("Roles :"+channel.getRoles());
Related
At the deserialization process (which as I understand is the process of converting JSON data into a Java Object), how can I tell Jackson that when it reads a object that contains no data, it should be ignored?
I'm using Jackson 2.6.6 and Spring 4.2.6
The JSON data received by my controller is as follows:
{
"id": 2,
"description": "A description",
"containedObject": {}
}
The problem is that the object "containedObject" is interpreted as is and it's being instantiated. Therefore, as soon as my controller reads this JSON data, it produces an instance of the ContainedObject object type but I need this to be null instead.
The easiest and fastest solution would be that in the JSON data received, this value be null like this:
{
"id": 2,
"description": "A description",
"containedObject": null
}
But this isn't possible since I'm not in control of the JSON data that is sent to me.
Is there an annotation (like this explained here) that works for the deserialization process and could be helpfull in my situation?
I leave a representation of my classes for more information:
My entity class is as follows:
public class Entity {
private long id;
private String description;
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
And my contained object class as follows:
public class ContainedObject {
private long contObjId;
private String aString;
//Contructor, getters and setters omitted
}
I would use a JsonDeserializer. Inspect the field in question, determine, if it is emtpy and return null, so your ContainedObject would be null.
Something like this (semi-pseudo):
public class MyDes extends JsonDeserializer<ContainedObject> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
//read the JsonNode and determine if it is empty JSON object
//and if so return null
if (node is empty....) {
return null;
}
return node;
}
}
then in your model:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = MyDes.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
Hope this helps!
You can implement a custom deserializer as follows:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = EmptyToNullObject.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
public class EmptyToNullObject extends JsonDeserializer<ContainedObject> {
public ContainedObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
long contObjId = (Long) ((LongNode) node.get("contObjId")).numberValue();
String aString = node.get("aString").asText();
if(aString.equals("") && contObjId == 0L) {
return null;
} else {
return new ContainedObject(contObjId, aString);
}
}
}
Approach 1 : This is mostly used. #JsonInclude is used to exclude properties with empty/null/default values.Use #JsonInclude(JsonInclude.Include.NON_NULL) or #JsonInclude(JsonInclude.Include.NON_EMPTY) as per your requirement.
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
private String empId;
private String firstName;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String lastName;
private String address;
private String emailId;
}
More info about the jackson annotations : https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
Approach 2 : GSON
use GSON (https://code.google.com/p/google-gson/)
I have the following structure:
public class User {
private Account account;
//constuctors, getters and setters
}
public class Account {
private String id;
private String description;
//constructor, getters and setters
}
When I performing the request I need to create the following JSON structure:
{
"account":
{
"id": "1",
"description": "Some description"
}
}
But I want to specify this information in a short way and ignore(left 'null') the 'description' field in the following way:
{
"account": "1" // I want to set directly the id field in the account object.
}
How may I do it? I tried #JsonCreator annotation and #JsonUnwrapped but without result.
You can use a custom deserializer
public class AccountFromIdDeserializer extends StdDeserializer<Account> {
public AccountFromIdDeserializer() { this(null);}
protected AccountFromIdDeserializer(Class<Account> type) { super(type);}
#Override
public Account deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
Account account = new Account();
account.setId(parser.getValueAsString());
return account;
}
}
And use on account node of User using #JsonDeserialize
public class User {
#JsonDeserialize(using = AccountFromIdDeserializer.class)
private Account account;
//constuctors, getters and setters
}
Finally I used #JsonCreator annotation and created two constructors:
#JsonCreator
public Account(#JsonProperty("id") String id, #JsonProperty("description") String description) {
this.id = id;
this.description = description;
}
#JsonCreator
public Account(String id) {
this.id = id;
}
I have the following entity which I use as a target POJO for one of the requests to a controller:
Entity
#Table(name="user_account_entity")
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonSerialize(using = UserAccountSerializer.class)
public class UserAccountEntity implements UserDetails {
//...
private String username;
private String password;
#PrimaryKeyJoinColumn
#OneToOne(mappedBy= "userAccount", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserEntity user;
#PrimaryKeyJoinColumn
#OneToOne(mappedBy= "userAccount", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserAccountActivationCodeEntity activationCode;
#JsonCreator
public UserAccountEntity(#JsonProperty(value="username", required=true) final String username, #JsonProperty(value="password", required=true) final String password) {
//....
}
public UserAccountEntity() {}
//.....
}
When I put unexpected fields in the request, it throws MismatchedInputException and fails with this message:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.myproject.project.core.entity.userAccountActivationCode.UserAccountActivationCodeEntity` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('9WL4J')
at [Source: (PushbackInputStream); line: 4, column: 20] (through reference chain: com.myproject.project.core.entity.userAccount.UserAccountEntity["activationCode"])
In the controller I have:
#InitBinder
public void binder(WebDataBinder binder) {
binder.addValidators(new CompoundValidator(new Validator[] {
new UserAccountValidator(),
new UserAccountActivationCodeDTOValidator() }));
}
And the endpoint that I make request to is:
#Override
public UserAccountEntity login(#Valid #RequestBody UserAccountEntity account,
HttpServletResponse response) throws MyBadCredentialsException, InactiveAccountException {
return userAccountService.authenticateUserAndSetResponsenHeader(
account.getUsername(), account.getPassword(), response);
}
Update 1
The code for UserAccountSerializer:
public class UserAccountSerializer extends StdSerializer<UserAccountEntity> {
public UserAccountSerializer() {
this(null);
}
protected UserAccountSerializer(Class<UserAccountEntity> t) {
super(t);
}
#Override
public void serialize(UserAccountEntity value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("id", value.getId());
gen.writeStringField("username", value.getUsername());
gen.writeEndObject();
}
}
The error is triggered because you have in your json :
"activationCode" : "9WL4J"
However Jackson does not know how to map the string "9WL4J" to the object UserAccountActivationCodeEntity
I guess the string "9WL4J" is the value of the primary key id of UserAccountActivationCodeEntity, in which case you should have in the json :
"activationCode" : {"id" : "9WL4J"}
If it is not the case use a custom Deseralizer to tell Jackson how to map the string to the object. You could use #JsonDeserialize on your entity.
I have such an entity:
public class User {
private Long id;
private String email;
private String password;
private List<Role> roles;
//set of constructors, getters, setters...
and related JSON:
[
{
"email": "user#email",
"roles": ["REGISTERED_USER"]
}
]
I'm trying to deserialize it in Spring MVC #Controller in such way:
List<User> users = Arrays.asList(objectMapper.readValue(multipartFile.getBytes(), User[].class));
Before adding List<Role> it worked perfect, but after I still have no luck. It seems I need some custom deserializer, could you help with approach for solving? Thank you!
If you have access to Role class you can just add such constructor:
private Role(String role) {
this.role = role;
}
or static factory methods:
#JsonCreator
public static Role fromJson(String value){
Role role = new Role();
role.setRole(value);
return role;
}
#JsonValue
public String toJson() {
return role;
}
Otherwise you will have to write custom deserealizer and register it on object mapper like this:
public static class RoleSerializer extends JsonSerializer {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeString(((Role) value).getRole());
}
}
public static class RoleDeserializer extends JsonDeserializer {
#Override
public Role deserialize(JsonParser jsonParser,DeserializationContext deserializationContext) throws IOException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
Role role = new Role();
role.setRole(node.asText());
return role;
}
}
Here is demo: https://gist.github.com/varren/84ce830d07932b6a9c18
FROM: [{"email": "user#email","roles": ["REGISTERED_USER"]}]
TO OBJ: [User{id=null, email='user#email', password='null', roles=Role{role='REGISTERED_USER'}}]
TO JSON:[{"email":"user#email","roles":["REGISTERED_USER"]}]
Ps:
If you use ObjectMapper like this
Arrays.asList(objectMapper.readValue(multipartFile.getBytes(), User[].class));
then code from demo will work, but you will probably have to set custom Bean in Spring for jackson ObjectMapper to make RoleDeserializer and RoleSerializer work everywhere. Here is more info:
Spring, Jackson and Customization (e.g. CustomDeserializer)
I am attempting to deserialise a response entity into a list of POJOs. When I do this directly, using a GenericType like so:
private List<UserRole> extractMembersDirectly(final ClientResponse response) {
return response.getEntity(new GenericType<List<UserRole>>() {});
}
I get this exception:
com.sun.jersey.api.client.ClientHandlerException: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (START_OBJECT), expected VALUE_STRING: need JSON String that contains type id (for subtype of java.util.List)
However, I can deserialise successfully when I use an ObjectMapper directly:
private List<UserRole> extractMembersUsingMapper(final ClientResponse response) throws IOException {
String json = response.getEntity(String.class);
ObjectMapper mapper = new ObjectMapperFactory().build();
return mapper.readValue(json, new TypeReference<List<UserRole>>() {});
}
The POJO is just:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonSnakeCase
public class UserRole {
private UUID id;
public UserRole(#JsonProperty("id") final UUID id) {
this.id = id;
}
public UUID getId() {
return id;
}
Is there a way to directly deserialise from the entity without first deserialising to String?
you can try having a custom serializer on the class itself
Writing the serializer
public class UserRoleSerializer extends JsonSerializer<Item> {
#Override
public void serialize(UserRole userRole, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("id", userRole.id);
jgen.writeEndObject();
}
}
Now registering the serializer to your class
#JsonSerialize(using = UserRoleSerializer.class)
public class UserRole {
...
}
Then Just an idea, not sure if thats what you looking for