This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”

demand

Recently, I made a requirement to invoke the REST interface of another service. It felt simple enough to get started quickly

To construct the Request class

public class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age; }}Copy the code

Pop, I come up a new

service.sendRequest(new User("niu".18));
Copy the code

It’s been another day of hard work.

positioning

However, one night at 8 PM, the tester called me out of the blue to say that the call had failed, and because of the lack of printing itself, there was no way to specify what was wrong.

I would not think that such a simple code itself will be wrong, no way!!

After the network capture, I found that the received parameters are null, but I clearly called the constructor passed the parameters

Is there something supernatural going on?

After analysis, the overall data flow is:

The only place you can have a problem is where you serialize JSON, so the local tests confirm this:

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    String request = objectMapper.writeValueAsString(new User("niu".18));
    System.out.println(request);
}
Copy the code

There is a problem, but instead of turning the serialization into an object with null properties, the exception is thrown directly

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class online.jvm.bean.User and no properties discovered to create BeanSerializer (to avoid exception.disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java: 77).Copy the code

By querying the exception data, this exception is resolved by adding Jackson’s serialization configuration FAIL_ON_EMPTY_BEANS, which indicates that if a bean serializes to null, it will not fail

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(FAIL_ON_EMPTY_BEANS, false);
    String request = objectMapper.writeValueAsString(new User("niu".18));
    System.out.println(request);
}
Copy the code

Instead of reporting an error, it returns an empty string, which results in receiving null attributes

Looking at the self-developed RPC framework, you can see that there is a configuration for the FAIL_ON_EMPTY_BEANS

To solve

Again, Jackson needs to call the bean’s getter method when serializing

1, write the getter and look at the result:

public class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName(a) {
        return name;
    }

    public Integer getAge(a) {
        return age;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu".18));
        System.out.println(request);
        {"name":"niu","age":18}}}Copy the code

2. Or change the property access to public

public class User {
    public String name;
    public Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu".18));
        System.out.println(request);
        {"name":"niu","age":18}}}Copy the code

But what if you want the properties of the bean not to be exposed even the getter?

3, annotations,@JsonProperty

This is done using the @jsonProperty annotation provided by Jackson

public class User {
    @JsonProperty("userName")
    private String name;
    @JsonProperty
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu".18));
        System.out.println(request);
        // {"userName":"niu","age":18}}}Copy the code

Take a look at the source annotation for @jsonProperty

Marker annotation that can be used to define a non-static method as a "setter" or "getter" for a logical property (depending on its signature), or non-static object field to be used (serialized, deserialized) as a logical property.
Copy the code

An annotation, if used on a property, is equivalent to defining a getter and setter for that property.

So if you have a getter and you have an @jsonProperty annotation, which one is going to prevail?

public class User {
    @JsonProperty("userName")
    private String name;
    @JsonProperty
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName(a) {
        return name;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu".18));
        System.out.println(request);
        // {"age":18,"userName":"niu"}}}Copy the code

What if you get the getter for a property that’s not there?

public class User {
    @JsonProperty("userName")
    private String name;
    @JsonProperty
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName2(a) {
        return name;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu".18));
        System.out.println(request);
        // {"age":18,"name2":"niu","userName":"niu"}}}Copy the code

That means if there is@JsonPropertyComments, comments shall prevail first

Then use reflection to find all the get methods of the object class, then go to get, and then lowercase, as each key value of json, and the return value of the get method as value. Next, reflect the field and add it to json.

4. Special circumstances

There is a special case where a getter method is generated by Lombok and the property’s sub-first letter is uppercase:

@Getter
public class User {
    @JsonProperty
    private String nAme;
    @JsonProperty
    private Integer age;

    public User(String name, Integer age) {
        this.nAme = name;
        this.age = age;
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String request = objectMapper.writeValueAsString(new User("niu".18));
        System.out.println(request);
        // {"nAme":"niu","age":18,"name":"niu"}}}Copy the code

This is because the getter lombok generates will capitalize the first letter of a property,

So what happens when you serialize is you change the uppercase between get and lowercase to lowercase, so you change NA to lowercase

So the serialization result will have two properties: name (getter) and name (annotation)

public String getNAme(a) {
    return this.nAme;
}
Copy the code

If we generate the getter ourselves with the idea shortcut,

After that, serialize the nAme

public String getnAme(a) {
    return nAme;
}
Copy the code

summary

A lot of bugs occur in places where you think you don’t have a problem. It looks simple, but you need to be careful. At the same time, you need to pay more attention to the principle of serialization.

This article covers the bug process and solution hope to be helpful to you, bye.