99

Am using the latest version of Spring Boot to read in a sample JSON via Restful Web Service...

Here's my pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"       
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/maven-v4_0_0.xsd"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>myservice</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.2.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.7</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-rest-webmvc</artifactId>
        </dependency>
        <dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
        <repository>
            <id>org.jboss.repository.releases</id>
            <name>JBoss Maven Release Repository</name>
            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>

Here's my web service code:

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/myservice")
public class BaseService {  

    @RequestMapping(value="/process", method = RequestMethod.POST)
    public void process(@RequestBody String payload) throws Exception {
        System.out.println(payload);
    }
}

When I invoke it using the following command:

curl -H "Accept: application/json" -H "Content-type: application/json" 
-X POST -d '{"name":"value"}' http://localhost:8080/myservice/process

I get this error message:

{"timestamp":1427515733546,"status":400,
 "error":"Bad Request",

"exception":
"org.springframework.http.converter.HttpMessageNotReadableException","
 message":
 "Could not read JSON: Can not deserialize instance of java.lang.String
  out of START_OBJECT token\n at 

 [Source: java.io.PushbackInputStream@8252f; line: 1, column: 1]; 
  nested    exception is com.fasterxml.jackson.databind.JsonMappingException:
  Can not deserialize instance of java.lang.String out of START_OBJECT token\n    
  at [Source: java.io.PushbackInputStream@8252f; line: 1, column: 1]",
  "path":"/myservice/process"

The only thing I am trying to do is pass in some valid JSON (as a string via curl) and to see if the String payload enters the process method as {"name":"value"}

What am I possibly doing wrong?

Thank you for taking the time to read this...

PacificNW_Lover
  • 4,746
  • 31
  • 90
  • 144
  • You have an error in your `pom.xml`: an useless/unclosed tag `` before ``. But is not that your problem... – Andrea Mar 29 '15 at 14:26

6 Answers6

181

I think the simplest/handy way to consuming JSON is using a Java class that resembles your JSON: https://stackoverflow.com/a/6019761

But if you can't use a Java class you can use one of these two solutions.

Solution 1: you can do it receiving a Map<String, Object> from your controller:

@RequestMapping(
    value = "/process", 
    method = RequestMethod.POST)
public void process(@RequestBody Map<String, Object> payload) 
    throws Exception {

  System.out.println(payload);

}

Using your request:

curl -H "Accept: application/json" -H "Content-type: application/json" \
-X POST -d '{"name":"value"}' http://localhost:8080/myservice/process

Solution 2: otherwise you can get the POST payload as a String:

@RequestMapping(
    value = "/process", 
    method = RequestMethod.POST,
    consumes = "text/plain")
public void process(@RequestBody String payload) throws Exception {

  System.out.println(payload);

}

Then parse the string as you want. Note that must be specified consumes = "text/plain" on your controller. In this case you must change your request with Content-type: text/plain:

curl -H "Accept: application/json" -H "Content-type: text/plain" -X POST \
-d '{"name":"value"}' http://localhost:8080/myservice/process
Community
  • 1
  • 1
Andrea
  • 15,900
  • 18
  • 65
  • 84
  • 1
    This worked! I just tried your second solution... Thank you very much. The issue is that this service is going to have different JSON coming in all the time and it doesn't have a schema... Would Map and also String payload be able to accommodate any type of JSON correctly? – PacificNW_Lover Mar 30 '15 at 03:29
  • @socal_javaguy The first solution will parse any JSON for you into a `Map` object. If you are using the second solution will be your care to validate and parse the string to a JSON, for example using this: http://www.baeldung.com/jackson-json-to-jsonnode – Andrea Mar 30 '15 at 09:58
  • I'm getting 404 error when the JSON contains Long data type https://stackoverflow.com/questions/64382824/404-spring-controller-error-if-json-string-contains-number-greater-than-10-digit/64382943?noredirect=1#comment113847835_64382943 – vishal sundararajan Oct 19 '20 at 04:43
  • I have a similar controller, but got another problem I got undefined chars when the string comes with special characters like ç ou õ, tried to set the charset with consumes = "text/plain; charset=utf-8", but no luck. Anyone? – Riberto Junior Feb 02 '22 at 19:27
  • @Roberto Junior Perhaps try base64 encoding your input. So your String payload will need to be decoded on receiving it. – Obothlale Jul 15 '22 at 09:55
22

To receive arbitrary Json in Spring-Boot, you can simply use Jackson's JsonNode. The appropriate converter is automatically configured.

    @PostMapping(value="/process")
    public void process(@RequestBody com.fasterxml.jackson.databind.JsonNode payload) {
        System.out.println(payload);
    }
Mateusz Stefek
  • 3,478
  • 2
  • 23
  • 28
  • like a charm! tks – Joao Moreno Nov 20 '20 at 05:09
  • I'm getting this error with this approach: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fasterxml.jackson.databind.JsonNode]: Is it an abstract class?; nested exception is java.lang.InstantiationException at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:215) ~[spring-beans-5.3.18.jar:5.3.18] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Do you know what could be the problem? – Luís Cunha Apr 28 '22 at 17:04
  • @LuísCunha I just make a simple example and this works. Could you post your code? – LiberiFatali Apr 29 '22 at 15:35
15

To add on to Andrea's solution, if you are passing an array of JSONs for instance

[
    {"name":"value"},
    {"name":"value2"}
]

Then you will need to set up the Spring Boot Controller like so:

@RequestMapping(
    value = "/process", 
    method = RequestMethod.POST)
public void process(@RequestBody Map<String, Object>[] payload) 
    throws Exception {

    System.out.println(payload);

}
S. Pan
  • 619
  • 7
  • 13
5

To further work with array of maps, the followings could help:

@RequestMapping(value = "/process", method = RequestMethod.POST, headers = "Accept=application/json")
public void setLead(@RequestBody Collection<? extends Map<String, Object>> payload) throws Exception {

  List<Map<String,Object>> maps = new ArrayList<Map<String,Object>>();
  maps.addAll(payload);

}
Rusli Usman
  • 111
  • 1
  • 7
2

The issue appears with parsing the JSON from request body, tipical for an invalid JSON. If you're using curl on windows, try escaping the json like -d "{"name":"value"}" or even -d "{"""name""":"value"""}"

On the other hand you can ommit the content-type header in which case whetewer is sent will be converted to your String argument

Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • Thanks Master Slave, the reason I didn't use a POJO is because this service is going to receive different types of JSON (containing different content)... Do you understand what I am trying to ask? – PacificNW_Lover Mar 28 '15 at 04:43
  • I understand (now). On the other hand, your server side is in fact OK. Its complaining about not being able to parse json, editing an answer – Master Slave Mar 28 '15 at 05:23
  • I am using bash from OS X Mavericks... What I am trying to do is pass in a JSON so I can parse it to return a custom response whether its valid or not. This is the first step is to first get a JSON string from the Post. Am I doing it wrong? – PacificNW_Lover Mar 28 '15 at 05:33
  • mhm, well, if I understand you correctly, not really. The fact that you send a content-type header set to application/json means that the conversion will be attempted before the request reaches handler method, and for invalid json this will fail with bad request 400. But, what you should do is send without the content-type header, than the request will reach your method, and there you can programatically try the conversion and return the appropriate response. Not sure if this is a viable approach for you, but its one that we'll work for sure – Master Slave Mar 28 '15 at 05:40
  • without the content type it returned this: %7B%22name%22%3A%22value%22%7D= – PacificNW_Lover Mar 30 '15 at 03:17
1

Jackson Library is what we need for Inject a Json string directly into the response without any extra parsing.

In cases where we have an already-escaped property and need to serialize it without any further escaping, we may want to use Jackson’s @JsonRawValue annotation on that field.

also in its documents has pointed :

Marker annotation that indicates that the annotated method or field should be serialized by including literal String value of the property as is, without quoting of characters. This can be useful for injecting values already serialized in JSON or passing javascript function definitions from server to a javascript client. Warning: the resulting JSON stream may be invalid depending on your input value.

Here is my DTO class:

public class UserProfileResponse {

    private Long id;

    private String userName;

    @JsonRawValue
    private String profile;

    @JsonRawValue
    private String settings;
}

without using @JsonRawValue my response would be something as below, because my profile and settings fields are fetching as JSON from my Database(Oracle for example), and spring serialize them again when wants to create a response for client:

{
    "id": 25,
    "userName": "admin",
    "profile": "{ \"gender\": \"male\", \"nationalId\": \"0123456789\", \"contacts\": { \"phone\": \"02112341234\" } }",
    "settings": "{\"lang\":\"fa-IR\",\"cols\":[],\"watch_lists\":{\"list_2\":[\"2\",\"3\"],\"list_1\":[\"1\",\"2\",\"3\"]}}"
}

but when I place @JsonRawValue on those variables, the response would serialize Like this:

{
    "id": 25,
    "userName": "admin",
    "profile": {
        "gender": "male",
        "nationalId": "0123456789",
        "contacts": {
            "phone": "02112341234"
        }
    },
    "settings": {
        "lang": "fa-IR",
        "cols": [],
        "watch_lists": {
            "list_2": [
                "2",
                "3"
            ],
            "list_1": [
                "1",
                "2",
                "3"
            ]
        }
    }
}
Sobhan
  • 1,280
  • 1
  • 18
  • 31