1

I am using SpringBoot and trying to deserialize JSON like:

{
  "userId": "Dave",
  "queryResults": {
    "id": "ABC",
    "carData": {.....},
    "carId": "Honda",
    "status": 0,
    "model": "X"
  }
}

, into MyRequestModel clas:

public class MyRequestModel {
   private String userId;
   private String: queryResults;
}

, that is received as @RequestBody parameter in my @PostMapping method that looks like:

    @PostMapping
    public String postDate(@RequestBody MyRequestModel data) {
        ...
        
        return "posted";
    }

The above queryResults field is supposed to be stored as a CLOB in a database.

Problem I am having is that if I send this JSON to hit my endpoint (PostMapping) method, it cannot deserialize it into MyRequestModel and I get this error:

Cannot deserialize instance of java.lang.String out of START_OBJECT token at [Source: (PushbackInputStream); line: 3, column: 18] (through reference chain: MyRequestModel["queryResults"])]

3 Answers 3

2

I guess the real answer to your question is: if you NEED the queryResults property to be a String, then implement a custom deserializer.

If not, then, use one of the alternatives that Jonatan and Montaser proposed in the other answers.

Implementing a custom deserializer within Spring Boot is fairly straightforward, since Jackson is its default serializer / deserializer and it provides a easy way to write our own deserializer.

First, create a class that implements the StdDeserializer<T>:

MyRequestModelDeserializer.java

public class MyRequestModelDeserializer extends StdDeserializer<MyRequestModel> {

    public MyRequestModelDeserializer() {
        this(null);
    }

    public MyRequestModelDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public MyRequestModel deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        JsonNode node = p.getCodec().readTree(p);

        String userId = node.get("userId").asText();
        String queryResults = node.get("queryResults").toString();

        MyRequestModel model = new MyRequestModel();
        model.setQueryResults(queryResults);
        model.setUserId(userId);

        return model;
    }

}

Second, mark your class to be deserialized using your custom deserializer by using the @JsonDeserialize annotation:

MyRequestModel.java

@JsonDeserialize(using = MyRequestModelDeserializer.class)
public class MyRequestModel {
    private String userId;
    private String queryResults;
}

It's done.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you. That looks exactly what I need however the json object for queryResults is retrieved as empty string for some reason. Any idea why would above line reading queryResults from JSON node return empty string?
It's working fine for me. Could you, please, show me your code so I can see if there's something wrong with your implementation?
Could you elaborate the 2 constructors above? I read few articles but non explain the meaning what they do and why they are needed. Especially the one with this(null) call? Also, I changed .asText() to .toString() and am just about to retest to see if that will work.
. . . My mistake, answer edited. Use toString() on node.get("queryResults") to retrieve the JSON. asText() doesn't work for object type fields of JSON. . . Now, for your question, the default constructor with this(null) is used by Jackson's ObjectMapper when creating the deserializer instance. . . The other constructor - MyRequestModelDeserializer(Class<?> vc) - you can even omit it and do just public MyRequestModelDeserializer(){this(MyRequestModel.class);}. This constructor is used to set the handled class to the StdDeserializer so you can access the handled type at Runtime.
Thanks, yes, I am using this(MyRequestModel.class) instead and using .asText() for userId (as that does not add 2 additional characters which are the quotation marks around userId so my DB field length does not become to short) and .toString() to get the queryResults CLOB string value. Much appreciated Matheus~
1

queryResults is a String on Java side but it is an Object on JSON side. You will be able to deserialize it if you send it in as a String:

{
  "userId": "Dave",
  "queryResults": "foo"
}

or if you create classes that maps to the fields:

public class MyRequestModel {
   private String userId;
   private QueryResults queryResults;
}

public class QueryResults {
   private String id;
   private CarData carData;
   private String carId;
   private Integer status;
   private String model;
}

or if you serialize it into something generic (not recommended):

public class MyRequestModel {
   private String userId;
   private Object queryResults;
}
public class MyRequestModel {
   private String userId;
   private Map<String, Object> queryResults;
}
public class MyRequestModel {
   private String userId;
   private JsonNode queryResults;
}

2 Comments

Much appreciated Jonatan. I think I might consider doing that in next iteration but right now, the requirement is to use CLOB to store that value into DB as CLOB.
As far as I remember, you will get what you need if you call toString() on the JsonNode (see my last example). In that case, you don't need a custom logic to deserialize the request, it will be a little more readable and flexible. Just do String.valueOf(request.getQueryResults()) when you create the object that you push to the DB.
1

You have two options to deserialize this request:-

  1. change the type of queryResults to Map<String, Object>, it will accepts everything as an object of key and value. (Not recommended)
public class MyRequestModel {
   private String userId;
   private Map<String, Object> queryResults;
}
  1. You have to create a class that wraps the results of queryResults as an object.
class QueryResult {

    private String id;

    private Map<String, Object> carData;

    private String carId;

    private Integer status;

    private String model;

    public QueryResult() {}

    public QueryResult(String id, Map<String, Object> carData, String carId, Integer status, String model) {
        this.id = id;
        this.carData = carData;
        this.carId = carId;
        this.status = status;
        this.model = model;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Map<String, Object> getCarData() {
        return carData;
    }

    public void setCarData(Map<String, Object> carData) {
        this.carData = carData;
    }

    public String getCarId() {
        return carId;
    }

    public void setCarId(String carId) {
        this.carId = carId;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }
}

and make the type of queryResult as shown:-

public class MyRequestModel {
   private String userId;
   private QueryResult queryResults;
}

1 Comment

Much appreciated Montaser. For now, I will have to stick with String as that is the requirement but that might change. So, for now I have to store it into DB as CLOB so it has to be string.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.