Search code examples
javaspring-bootjacksonjson-deserialization

Jackson Custom Deserializer Not Working In Spring Boot


I created a custom deserializer for my entities but it keeps on throwing exception:

I have two classes: AppUser and AppUserAvatar

AppUser.java

@Entity
@Table(name = "user")
public class AppUser implements Serializable {

    @Transient
    private static final long serialVersionUID = -3536455219051825651L;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @Column(name = "password", nullable = false, length = 256)
    private String password;

    @JsonIgnore
    @Column(name = "is_active", nullable = false)
    private boolean active;

    @JsonIgnore
    @OneToMany(mappedBy = "appUser", targetEntity = AppUserAvatar.class, fetch = FetchType.LAZY)
    private List<AppUserAvatar> appUserAvatars;

    //// Getters and Setters and toString() ////
}

AppUserAvatar.java

@Entity
@Table(name = "user_avatar")
public class AppUserAvatar extends BaseEntityD implements Serializable {

    @Transient
    private static final long serialVersionUID = 8992425872747011681L;

    @Column(name = "avatar", nullable = false)
    @Digits(integer = 20, fraction = 0)
    @NotEmpty
    private Long avatar;

    @JsonDeserialize(using = AppUserDeserializer.class)
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private AppUser appUser;

    //// Getters and Setters and toString() ////
}

AppUserDeserializer.java package com.nk.accountservice.deserializer;

import com.edoctar.accountservice.config.exception.InputNotFoundException;
import com.edoctar.accountservice.domain.candidate.AppUser;
import com.edoctar.accountservice.service.candidate.AppUserService;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.IOException;
import java.io.Serializable;

public class AppUserDeserializer extends JsonDeserializer implements Serializable {

    private static final long serialVersionUID = -9012464195937554378L;

    private AppUserService appUserService;

    @Autowired
    public void setAppUserService(AppUserService appUserService) {
        this.appUserService = appUserService;
    }

    @Override
    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        Long userId = node.asLong();
        System.out.println(node);
        System.out.println(node.asLong());
        AppUser appUser = appUserService.findById(userId);
        System.out.println("appuser: " + appUser);
        if (appUser == null) try {
            throw new InputNotFoundException("User not found!");
        } catch (InputNotFoundException e) {
            e.printStackTrace();
            return null;
        }

        return appUser;
    }
}

Sample xhr boy is:

{
  "appUser": 1,
  "avatar": 1
}

An exception is thrown each time I submit the request.

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.edoctar.accountservice.domain.candidate.AppUserAvatar["appUser"])]

Here's the screenshoot of my console.

I discovered that the appUserService.findById() method is not being called. I am really confused. I don't know where I went wrong. Will be greatful for any solution. Thanks.


Solution

  • Updated answer: You can't use autowired properties because you are not in the Spring context. You are passing the class AppUserDeserializer as a reference in the annotation

    @JsonDeserialize(using = AppUserDeserializer.class)
    

    In this situation is the FasterJackson library that creates the instance of AppUserDeserializer, so the Autowired annotation is not taken in consideration.

    You can solve your problem with a little trick. Add a static reference to the instance created by spring in the AppUserService:

     @Service
     public AppUserService {
    
       public static AppUserService instance;
    
       public AppUserService() {
         // Modify the constructor setting a static variable holding a
         // reference to the instance created by spring
         AppUserService.instance = this;
       }
    
       ...
     }
    

    Use that reference in the AppUserDeserializer:

    public class AppUserDeserializer extends JsonDeserializer implements Serializable {
    
        private AppUserService appUserService;
    
        public AppUserDeserializer() {
          // Set appUserService to the instance created by spring
          this.appUserService = AppUserService.instance;
        }
    
        ...
    
    }
    

    Original answer: To have a correct initialization of an Autowired property you have to annotate your class AppUserDeserializer, otherwise appUserService is null if you don't explicitly init it using the set method.

    Try to annotate AppUserDeserializer with @Component:

    @Component   // Add this annotation
    public class AppUserDeserializer extends JsonDeserializer implements Serializable {
       ...
    }