1
votes

I am using the below JSON to get parsed by my springboot code:

{
    "id" :123, 
    "Subject" : "English",
    "name": {
    "Firstname": "Hemant",
    "Middlename": "kumar", 
    "Lastname": "Mamod"
    }
}

But on parsing/deserializing it I am getting this in the object of Student:

Student [id=123, Subject=null, name=Name [Firstname=null, Middlename=null, Lastname=null]]

I aleady have public gettters & setters everywhere which makes every field eligible for deserialization. I am referring this - https://www.baeldung.com/jackson-field-serializable-deserializable-or-not

Below is the code snippet I am using:

Restcontroller:

    package com.example.demo;
    import java.util.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.*;


    @SpringBootApplication
    @RestController
    public class Demo1Application {


    @Autowired
    Student student;
    List<Student> students = new ArrayList<>();


    @RequestMapping(value="/saveStudent", method=RequestMethod.POST,consumes=MediaType.APPLICATION_JSON_VALUE)
    public String savestudent (@RequestBody Student s1) {

        System.out.println(s1);
        return "success";
    }   


    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class, args);
        System.out.println("Hello");
    }
    }

Student Bean Class:

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Student {
    int id;
    String Subject;

    @Autowired
    @Qualifier("name")
    Name name;


    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getSubjects() {
        return Subject;
    }
    public void setSubject(String subject) {
        Subject = subject;
    }

    public void setName(Name name) {
        this.name = name;  
    }
    public Name getName() {  
        return name  ;
    }
@Override
public String toString() {
    return "Student [id=" + id + ", Subject=" + Subject + ", name=" + name + "]";
    }
}

Name Bean Class:

package com.example.demo;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("name")
public class Name {
    private String Firstname;
    private String Middlename;
    private String Lastname;

    public Name() {
        System.out.println("Name Object created");
    }

    public String getFirstname() {
        return Firstname;
    }
    public void setFirstname(String firstname) {
        Firstname = firstname;
    }
    public String getMiddlename() {
        return Middlename;
    }
    public void setMiddlename(String middlename) {
        Middlename = middlename;
    }
    public String getLastname() {
        return Lastname;
    }
    public void setLastname(String lastname) {
        Lastname = lastname;
    }

    public Name setfullname(String Fname, String Mname, String Lname) {
        Firstname = Fname;
        Middlename = Mname;
        Lastname = Lname;
        return this;
    }

    @Override
    public String toString() {
        return "Name [Firstname=" + Firstname + ", Middlename=" + Middlename + ", Lastname=" + Lastname + "]";
    }

}

PS -

1.) On making the private fields as public , the code is working fine. I made these fields as public - id , Subject , name, Firstname, Middlename, Lastname

2.) I already know that by adding below two in application.properties wil give me correct output: spring.jackson.deserialization.fail-on-unknown-properties=false spring.jackson.mapper.accept-case-insensitive-properties=true

I just want to know where exactly I am doing things wrong. I have public gettters & setters everywhere

5
Take care about java naming convention. Property names should start with lower case characterJens
The issue might be caused by naming convention for gettters and setters in Java. Basically variable names start with lowercase letters, like firstname and getter/setter would be getFirstname()/setFirstname() but when your variables start with uppercase letter, parser can have problems with it.Kamil
@Kamil Maybe this is the problem. Let me try it out firstASharma7
@Kamil I changed property names with lower case. Still getting same output.ASharma7
Why is your student class annotated with @Component? Isn't it a DTO and not a Controller/Service which should not be injected anywhere?Markus G.

5 Answers

2
votes

Reformat your JSON input to have camel case keys:

{
    "id" :123, 
    "subject" : "English",
    "name": {
    "firstname": "Hemant",
    "middlename": "kumar", 
    "lastname": "Mamod"
    }
}
2
votes

Use the @JsonProperty annotation map the name of the property in json to the property name in java:

@JsonProperty("Firstname")
private String firstname;
@JsonProperty("Middlename")
private String middlename;
@JsonProperty("Lastname")
private String lastname;
1
votes

This is now working. Fix was to keep Both the property name & JSON in lower case for fields to be correctly Deserialized as metioned by Jens, James and Kamil. I have Changed property names as follows:

private String Subject;     -->  private String subject; 
private String Firstname;   -->  private String firstname;
private String Middlename;  -->  private String middlename;
private String Lastname;    -->  private String lastname;
1
votes

The reason for the json not getting accepted with the first character of attributes in upper case is because jackson library is resolving property names from getters and setters of the class corresponding to the json. Jackson constructs property names by converting the leading characters of text following 'get' to lower case(from getter method in java side). For example: getSubject in the Student class is kept in a property set as 'subject'.

When a post request is send with the json, attributes in the json is compared against the property set. Only those attribute names that match this property set will have their values added from json to corresponding java attribute. When you do post request with json attribute name 'Subject',jackson won't deserialize this . Since 'Subject' attribute name does not match with 'subject' in the property set. That is why the null was coming initially for those attributes.

The above said is the default behaviour.To override this behavior you can map the java property to desired json attribute name by using @JsonProperty annotation:

@JsonProperty("Subject")
String Subject;

Otherwise use the json attribute name starting with lower case.For eg:'subject' instead of 'Subject'

The conversion of leading chacracter case can be seen in com.fasterxml.jackson.databind.util.Beanutil class.The below method does this:

protected static String legacyManglePropertyName(final String basename, final int offset)
    {
        final int end = basename.length();
        if (end == offset) { // empty name, nope
            return null;
        }
        // next check: is the first character upper case? If not, return as is
        char c = basename.charAt(offset);
        char d = Character.toLowerCase(c);

        if (c == d) {
            return basename.substring(offset);
        }
        // otherwise, lower case initial chars. Common case first, just one char
        StringBuilder sb = new StringBuilder(end - offset);
        sb.append(d);
        int i = offset+1;
        for (; i < end; ++i) {
            c = basename.charAt(i);
            d = Character.toLowerCase(c);
            if (c == d) {
                sb.append(basename, i, end);
                break;
            }
            sb.append(d);
        }
        return sb.toString();
    }

The property name comparison against json attribute can be found in the below code(deserializeFromObject function in com.fasterxml.jackson.databind.deser.BeanDeserializer class. I am not pasting the entire function as it is too long). The variable p corresponds to Jsonparser and beanProperties is containing the propertynames resolved from getter and setter,deserializeAndSet method is setting json attribute value to the java bean:

 if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
            String propName = p.getCurrentName();
            do {
                p.nextToken();
                SettableBeanProperty prop = _beanProperties.find(propName);
                if (prop != null) { // normal case
                    try {
                        prop.deserializeAndSet(p, ctxt, bean);
                    } catch (Exception e) {
                        wrapAndThrow(e, bean, propName, ctxt);
                    }
                    continue;
                }
                handleUnknownVanilla(p, ctxt, bean, propName);
            } while ((propName = p.nextFieldName()) != null);
0
votes

In my case, I overrided the toString method in Springboot application with few fields (Not all the fields available in the Pojo). It worked fine when I deleted the toString method