JsonIgnoreProperties doesn't work with JsonCreator constructor - java

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.

Related

Jackson: Unable to deserialize a map attribute in Request Dto using annotations

I want to deserialize this json as Map<string, LocalizationDto>:
{
"DE": {
"name": "name1",
"description": "name1 description"
},
"EN": {
"name": "name2",
"description": "name2 description"
}
}
that represents the parameter "localizations" in this Request Dto:
#Getter
#Setter
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
#AllArgsConstructor
public class RequestDto {
#Size(max = 16)
#NotEmpty
private String code;
#NotNull
private Boolean stackable;
#NotNull
private MultipartFile file;
#JsonDeserialize(using = MapDeserializer.class )
#JsonProperty("localizations")
#NotEmpty
private Map<String, LocalizationDto> localizations;
#JsonCreator
public RequestDto() {
}
public static class MapDeserializer extends JsonDeserializer <Map<String, LocalizationDto>> {
public MapDeserializer() {
super();
}
#Override
public Map<String, LocalizationDto> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
if (jsonParser.getCurrentToken().equals(JsonToken.START_OBJECT)) {
return mapper.readValue(jsonParser, new TypeReference<>() {
});
} else {
//consume this stream
mapper.readTree(jsonParser);
return new HashMap<>();
}
}
}
}
The LocalizationDto class is:
#Getter
#Setter
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
#NoArgsConstructor
#AllArgsConstructor
public class LocalizationDto {
#NotBlank
#Size(max = 1024)
public String name;
#NotBlank
#Size(max = 1024)
public String description;
}
the Deserialization should be triggered by this Rest Request:
#PostMapping(value = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE , produces = MediaType.APPLICATION_JSON_VALUE)
public responseDto createEntity(Authentication authentication,
#ModelAttribute #Valid RequestDto requestDto) throws IOException {
return service.createEntity(requestDto));
}
}
After running this request I got a problem with the attribut "localizations":
"org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult:
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult
default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Map' for property 'localizations';
nested exception is java.lang.IllegalStateException:
It seems that Jackson is not considering my custom deserializer class MapDeserializer or any annotation is missing.
Thank you for the support

jackson deserialization with 2 fields self referencing the same class (self-reference cycle)

I have below class referencing to itself:
#Entity
#Inheritance(strategy = TABLE_PER_CLASS)
//#JsonIdentityInfo(property="rowId", generator = ObjectIdGenerators.PropertyGenerator.class)
public abstract class AbstractEntity implements Serializable {
/**
*
*/
private static final long serialVersionUID = 568799551343430329L;
#OneToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(name="createdBy")
protected User createdBy;
#OneToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(name="lastUpdatedBy")
protected User lastUpdatedBy;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(unique = true, nullable = false, updatable = false, length = 7)
private Integer rowId;
public User getCreatedBy() {
return this.createdBy;
}
public void setCreatedBy(User createdBy) {
this.createdBy = createdBy;
}
public User getLastUpdatedBy() {
return this.lastUpdatedBy;
}
public void setLastUpdatedBy(User lastUpdatedBy) {
this.lastUpdatedBy = lastUpdatedBy;
}
public Integer getRowId() {
return this.rowId;
}
public void setRowId(Integer RowId) {
this.rowId = RowId;
}
public String toString() {
return "[Id]:" + this.rowId + " - [CreatedBy]:" + this.createdBy;
}
}
Then I have a class User extending this class and a RepositoryUser interface:
public interface RepositoryUser extends CrudRepository<User, Integer> {
}
And a Controller:
#Controller
#RequestMapping(path = "/user")
public class ServiceUser {
#Autowired
private RepositoryUser repositoryUser;
#GetMapping(path="/all", produces = "application/json; charset=UTF-8", headers = "Accept=application/json")
public #ResponseBody Iterable<User> getAllUsers() {
return repositoryUser.findAll();
}
#PostMapping(path="/add", consumes="application/json")
public #ResponseBody User createOneUser(#RequestBody User user) {
System.out.println(user);
return repositoryUser.save(user);
}
}
My issue is that I'm making reference to User twice (createdby and lastupdatedby) in the same class and either I tried JSonIdentityInfo, Jsonmanaged,jsonback nothing works. correctly.
I need to be able to have
{
User 1 data including created by and last updated by
User 2 data including created by and last updated by
}
and when I add I need to set the user who creates the record.
Can you please help me ?
Thanks a lot!
You could write/try a Custom Serializer Using StdSerializer.
Example of CustomJsonSerializer. NB: Did not run the code.
public class CustomJsonSerializer extends StdSerializer<AbstractEntity> {
public CustomJsonSerializer() {
this(null);
}
public CustomJsonSerializer(Class<AbstractEntity> t) {
super(t);
}
#Override
public void serialize(AbstractEntity value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
Field[] fields = value.getClass().getDeclaredFields();
jgen.writeStartObject();
for (Field field : fields) {
field.setAccessible(true);
try {
// Do the proper field mapping for field types . Object type example
jgen.writeObjectField(field.getName(), field.get(value));
} catch (Exception e) {
// catch error
}
}
jgen.writeEndObject();
}
}
Then on your Rest Method use #JsonSerialize
#JsonSerialize(using = CustomJsonSerializer.class)
#GetMapping(path="/all", produces = "application/json; charset=UTF-8", headers = "Accept=application/json")
public #ResponseBody Iterable<User> getAllUsers() {
return repositoryUser.findAll();
}
Please see Custom Serializer And StdSerializer
Possible different solution
jackson-bidirectional infinite-recursion

Deserialize list of objects with inner list with Jackson

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)

Custom Jackson Deserializer to handle a property of any bean

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

How to properly deserialise ClientResponse entity to List of POJOs

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

Categories

Resources