0

I've encountered a problem in my application when sending request to create a single property object in the server.

When sending a Post/Put request with a json body, the only way the server accept the request is to flat the request - remove the curly brackets and only send the value of the class property, without the property name (the key in the json), while receiving it back as a nested json.

Sending the Request with the json received back, as nested object results in bad request, and so the situation is that the request and response are asymmetrical.

(I'm using String as an example, but the same happed with int, float etc)

in this example, I simplified the code to have 2 classes -

  1. TimeStamp
  2. TimeStampWrapper

I'm sending the request (the timestamp) as follows (flat):

"2023-02-19T16:08:41.897+00:00"

When receiving the object back, I receive it correctly (nested):

{
   "timestamp": "2023-02-19T16:08:41.897+00:00"
}

The server side code:

  1. The MyTimeStamp one property class:
package brokenjson.demo.demo;
import lombok.*;
@AllArgsConstructor
@ToString
@Getter
@Setter
public class MyTimeStamp {
    private String timestamp;
}
  1. The MyTimeWrapper class, which contains only MyTimeStamp as a property:
package brokenjson.demo.demo;

import lombok.*;

import java.beans.ConstructorProperties;

@ToString
@Getter
@Setter
public class MyTimeWrapper {
    private MyTimeStamp myTimeStamp;

    public MyTimeWrapper() {
    }
}

The Main/Controller:

package brokenjson.demo.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @PutMapping("/time")
    public MyTimeStamp gettingTimeStamp(@RequestBody MyTimeStamp myTimeStamp) {
        System.out.println(myTimeStamp);
        return myTimeStamp;
    }
    @PutMapping("/timeWrap")
    public MyTimeWrapper gettingTimeStamp(@RequestBody MyTimeWrapper myTimeWrapper) {
        System.out.println(myTimeWrapper);
        return myTimeWrapper;
    }
}

The reason I have a wrapping class in this demo is that the problem first arose when trying to send a request to create such a wrapping class, and not understanding why the request being sent is flat, and the response being received is nested.

The request:

{
   "myTimeStamp":"2023-02-19T16:08:41.897+00:00"
}

The response:

{
    "myTimeStamp": {
        "timestamp": "2023-02-19T16:08:41.897+00:00"
    }
}

I'm using Java 17 (although this problem also occurred when using java 19).

My pom.xml -

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>brokenjson.demo</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>

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

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

</project>

In my application I'm using webFlux, so I originally thought it was a problem with the webFlux deserializer or something like that, but when I created a new project with spring-boot-strter-web I observed the problem persistent.

Sidenote: In the "MyTimeWrapper" class, spring requested me to create an empty constructor to be able to receive the request, I don't believe it's related but I think it should be mentioned.

  • Try mentioning that your post request consumes json data by adding @Consumes(MediaType.APPLICATION_JSON) annotation above your method in the controller – Farha Mansuri Feb 20 '23 at 13:50
  • @FarhaMansuri thanks for the suggestion but it didn't work. It seems the problem isn't that the request is not expecting json, it's the format that it's expecting. – Yoav Zinger Feb 20 '23 at 14:01

1 Answers1

1

If you send a POST request to /time with "2023-02-19T16:08:41.897+00:00" as body, Spring wraps and interprets it as

{
  "timestamp": "2023-02-19T16:08:41.897+00:00"
}

So it doesn't matter whether you send

{
  "timestamp": "2023-02-19T16:08:41.897+00:00"
}

or just

"2023-02-19T16:08:41.897+00:00"

This only works when there is a single member variable in the request object's class and is not associated with any other class. Why it doesn't work when there is more than one object?

  • because that doesn't remain to be a JSON refer e.g. your controller accepts an object Timestamp which has 2 String members, time, and zone, and you send a request body as:
{
    "2023-02-19 16:08:41.89", 
    "PST"
}

, which is not even a valid JSON, and if it was then there is clear confusion as to which one is zone and which one is time. However, while sending back the response, spring always sends it in the expanded form, so you get

Why it doesn't work when the object is associated with another object?

  • because it will cause nesting issues e.g. class TimeStampWrapper has "has-a" relation with TimeStamp class. If we expect a TimeStampWrapper request and we pass String, then it would be treated as a bad request. So either you can pass:
{
   "myTimeStamp":"2023-02-19T16:08:41.897+00:00"
}

or

{
    "myTimeStamp": {
        "timestamp": "2023-02-19T16:08:41.897+00:00"
    }
}

as request body. And it doesn't matter how you send your request (flat or inside braces), the response will always be a json inside braces.

Also, by default, consumes accept JSON for a POST request- Consumes

S.Ashok
  • 111
  • 4
  • Hi, thanks you for the explanation, but unfortunately, when sending the nested jsons I receive 400 bad request from the server as you can see in this screenshot https://imgur.com/a/pCYo2Kf – Yoav Zinger Feb 20 '23 at 16:14
  • Have you used any other config? Also, What headers are you using in the request? – S.Ashok Feb 20 '23 at 16:53
  • Yes, I have used the same application but with webflux. I haven't added any header and sending the postman defaults – Yoav Zinger Feb 20 '23 at 21:15
  • In the image shared by you (of postman) I can clearly see 8 headers being passed in request . Also, if you have an issue with webflux code, please show the webflux code. – S.Ashok Feb 21 '23 at 04:02
  • Those are the headers - https://imgur.com/a/X9ZDIl2 – Yoav Zinger Feb 21 '23 at 08:59
  • Hi @YoavZinger, are you really using those headers on your server? If not, then try to uncheck or remove the `Content-Length` header (or rather all other headers other than Content-Type). – S.Ashok Feb 21 '23 at 15:03
  • > For Content-Length and Content-Type headers, Postman will automatically calculate values when you send your request, based on the data in the Body tab. However, you can override both values. refer- [docs](https://learning.postman.com/docs/sending-requests/requests/) – S.Ashok Feb 21 '23 at 15:11
  • and if you really want to override it, make sure your actual json length in bytes is less than the Content-Length specified. If you really want to know the content length of json request, then open Postman-console (from the bottom left), then fire a request by unchecking the `Content-Type`, you will be able to see the `Content-Length` field in the request header on the console. – S.Ashok Feb 21 '23 at 15:21