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
Related
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.
My question is kind of similar to Prevent GSON from serializing JSON string but the solution there uses GSON library and I am restricted to using Jackson (fasterxml).
I have an entity class as follows:
package com.dawson.model;
import com.dawson.model.audit.BaseLongEntity;
import lombok.extern.log4j.Log4j;
import javax.persistence.*;
#Table(name = "queue", schema = "dawson")
#Entity
#Log4j
public class Queue extends BaseLongEntity {
protected String requestType;
protected String body;
protected Queue() {
}
public Queue(String requestType, String body) {
this.requestType = requestType;
this.body = body;
}
#Column(name = "request_type")
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
#Column(name = "body")
#Lob
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
I want to populate the body field with the json string representation of a map and then send this as part of the ResponseEntity. Something as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.writer().withDefaultPrettyPrinter();
HashMap<String, String> map = new HashMap<>(5);
map.put("inquiry", "How Can I solve the problem with Jackson double serialization of strings?");
map.put("phone", "+12345677890");
Queue queue = null;
try {
queue = new Queue("General Inquiry", mapper.writeValueAsString(map));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
String test = mapper.writeValueAsString(map)
System.out.println(test);
Expected Output: "{"requestType": "General Inquiry","body": "{"inquiry":"How Can I solve the problem with Jackson double serialization of strings?","phone":"+12345677890"}"}"
Actual Output:"{"requestType": "General Inquiry","body": "{\"inquiry\":\"How Can I solve the problem with Jackson double serialization of strings?\",\"phone\":\"+12345677890\"}"}"
I am using
Jackson Core v2.8.2
I tried playing with
#JsonIgnore
and
#JsonProperty
tags but that doesn't help because my field is already serialized from the map when writing to the Entity.
Add the #JsonRawValue annotation to the body property. This makes Jackson treat the contents of the property as a literal JSON value, that should not be processed.
Be aware that Jackson doesn't do any validation of the field's contents, which makes it dangerously easy to produce invalid JSON.
I have a spring project and class like that and want to produce json with root name as type. Here is an example:
public class Person {
private String type; //worker
private String name; //Dennis
private String surname; //Ritchie
}
Result should be:
{"worker" : {
"name" : "Dennis" ,
"surname" : "Ritchie"
}
}
Can I do it with Json tags like #JsonRootName or should I write a Class for worker and extend Person class (There are 3 different types)?
You can implement a custom Serializer when you need to serialize a object into a JSON with a different form:
public class PersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person person, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeFieldName(person.getType());
jgen.writeStartObject();
jgen.writeFieldName("name", person.getName());
jgen.writeFieldName("surname", person.getSurname());
jgen.writeEndObject();
jgen.writeEndObject();
}
}
After that, you can register the serializer on the class:
#JsonSerialize(using = PersonSerializer.class)
public class Person {
private String type;
private String name;
private String surname;
}
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 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());