最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Spring Boot fails to return JSON for a object but not for list of objects - Stack Overflow

programmeradmin6浏览0评论

I am developing my first Spring Boot application and i ran into a weird problem. The configuration is very basic:

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=""
    xsi:schemaLocation=".0.0 .0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>.pawsec</groupId>
    <artifactId>kitchen</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>kitchen</name>
    <description>The Kitchen restaurant system</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency> 
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>.pawsec</groupId>
            <artifactId>mon</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

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


</project>

We have some Javascript code on a page calling these two services. When the controller returns a Guy object in the first method, we get an empty response:

    {data: "", status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: ""
headers: {}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object

When we return a List of Guy objects from the second method, however, we get the full Json structure

back:
{data: Array(3), status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: Array(3)
0: {guyId: 1, name: "Walter Sobchak", age: 45}
1: {guyId: 2, name: "Jeffrey Lebowski", age: 42}
2: {guyId: 3, name: "Theodore Donald Kerabatsos", age: 39}
length: 3
: Array(0)
headers: {content-type: "application/json;charset=UTF-8", cache-control: "private", expires: "Thu, 01 Jan 1970 00:00:00 GMT"}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object

The controller looks like this:

package .pawsec.kitchen.controller;

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

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

import .pawsec.kitchen.model.Guy;

@RestController
public class GuyController {

    @RequestMapping(value="/get/guy/{guyId}", method=RequestMethod.GET,
            headers={"Accept=application/json"})
    public Guy getGuy(@PathVariable("guyId") int guyId) {
        Guy someGuy = new Guy(guyId, "Walter Sobchak", 45);
        return someGuy;
    }

    @RequestMapping(value="/get/guys", method=RequestMethod.GET,
            headers={"Accept=application/json"})
    public List<Guy> getGuys() {
        Guy walter = new Guy(1, "Walter Sobchak", 45);
        Guy theDude = new Guy(2, "Jeffrey Lebowski", 42);
        Guy donny = new Guy(3, "Theodore Donald Kerabatsos", 39);
        List<Guy> guys = new ArrayList<Guy>();
        guys.add(walter);
        guys.add(theDude);
        guys.add(donny);
        return guys;
    }

}

Strangely, if i call these two services from a browser, i get the correct Json structure for both the calls.

When i run a mvn dependency:tree, the expected Jackson dependencies that e with a basic Boot Project are there.

This is what the JavaScript code looks like:

return dispatch => {
        dispatch(fetchMenuStart());
        const url = ':8443/get/guy/1'; 
        const headers = {
            headers: {
                'Content-Type': 'application/json'
            }
        }
        axios.get(url, headers)
            .then(res => {
                console.log(res); 
                dispatch(fetchMenuSuccess(res.data.categories, res.data.restaurant));
            })
            .catch(error => {   
                console.log("error", error);
                const errorMsg = 'There was an error fetching the menu';
                dispatch(fetchMenuFail(errorMsg)); 
            });
    };

Can anyone suggest what might be causing this or steps to test to figure out the issue?

New javascript example code:

const doesNotWork = ':8443/get/guy/1'; 
const doesWork = ':8443/get/guys'; 
const headers = {
    headers: {
    'Content-Type': 'application/json;charset=UTF-8'
    }
}
axios.get(doesNotWork, headers)
    .then(res => {
        console.log(res); 
    })
    .catch(error => {   
        console.log("error", error);
        const errorMsg = 'There was an error fetching the menu';
    });

I am developing my first Spring Boot application and i ran into a weird problem. The configuration is very basic:

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

    <groupId>.pawsec</groupId>
    <artifactId>kitchen</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>kitchen</name>
    <description>The Kitchen restaurant system</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency> 
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>.pawsec</groupId>
            <artifactId>mon</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

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


</project>

We have some Javascript code on a page calling these two services. When the controller returns a Guy object in the first method, we get an empty response:

    {data: "", status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: ""
headers: {}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object

When we return a List of Guy objects from the second method, however, we get the full Json structure

back:
{data: Array(3), status: 200, statusText: "", headers: {…}, config: {…}, …}
config: {adapter: ƒ, transformRequest: {…}, transformResponse: {…}, timeout: 0, xsrfCookieName: "XSRF-TOKEN", …}
data: Array(3)
0: {guyId: 1, name: "Walter Sobchak", age: 45}
1: {guyId: 2, name: "Jeffrey Lebowski", age: 42}
2: {guyId: 3, name: "Theodore Donald Kerabatsos", age: 39}
length: 3
: Array(0)
headers: {content-type: "application/json;charset=UTF-8", cache-control: "private", expires: "Thu, 01 Jan 1970 00:00:00 GMT"}
request: XMLHttpRequest {onreadystatechange: ƒ, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
status: 200
statusText: ""
: Object

The controller looks like this:

package .pawsec.kitchen.controller;

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

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

import .pawsec.kitchen.model.Guy;

@RestController
public class GuyController {

    @RequestMapping(value="/get/guy/{guyId}", method=RequestMethod.GET,
            headers={"Accept=application/json"})
    public Guy getGuy(@PathVariable("guyId") int guyId) {
        Guy someGuy = new Guy(guyId, "Walter Sobchak", 45);
        return someGuy;
    }

    @RequestMapping(value="/get/guys", method=RequestMethod.GET,
            headers={"Accept=application/json"})
    public List<Guy> getGuys() {
        Guy walter = new Guy(1, "Walter Sobchak", 45);
        Guy theDude = new Guy(2, "Jeffrey Lebowski", 42);
        Guy donny = new Guy(3, "Theodore Donald Kerabatsos", 39);
        List<Guy> guys = new ArrayList<Guy>();
        guys.add(walter);
        guys.add(theDude);
        guys.add(donny);
        return guys;
    }

}

Strangely, if i call these two services from a browser, i get the correct Json structure for both the calls.

When i run a mvn dependency:tree, the expected Jackson dependencies that e with a basic Boot Project are there.

This is what the JavaScript code looks like:

return dispatch => {
        dispatch(fetchMenuStart());
        const url = 'https://boot.ourpany.:8443/get/guy/1'; 
        const headers = {
            headers: {
                'Content-Type': 'application/json'
            }
        }
        axios.get(url, headers)
            .then(res => {
                console.log(res); 
                dispatch(fetchMenuSuccess(res.data.categories, res.data.restaurant));
            })
            .catch(error => {   
                console.log("error", error);
                const errorMsg = 'There was an error fetching the menu';
                dispatch(fetchMenuFail(errorMsg)); 
            });
    };

Can anyone suggest what might be causing this or steps to test to figure out the issue?

New javascript example code:

const doesNotWork = 'https://boot.exmpledomain.:8443/get/guy/1'; 
const doesWork = 'https://boot.exmpledomain.:8443/get/guys'; 
const headers = {
    headers: {
    'Content-Type': 'application/json;charset=UTF-8'
    }
}
axios.get(doesNotWork, headers)
    .then(res => {
        console.log(res); 
    })
    .catch(error => {   
        console.log("error", error);
        const errorMsg = 'There was an error fetching the menu';
    });
Share Improve this question edited Feb 26, 2019 at 14:21 Mats Andersson asked Feb 18, 2019 at 16:08 Mats AnderssonMats Andersson 3972 gold badges8 silver badges21 bronze badges 26
  • 2 did you set the content-type as application/json in the ajax call? – Amardeep Bhowmick Commented Feb 18, 2019 at 16:13
  • 2 If you're getting the right response from the browser but incorrect from the js code, obviously there is some issue with the js code. Could you please add the js code snippet that you're using? – Madhu Bhat Commented Feb 18, 2019 at 16:15
  • 1 ..maybe show us "some Javascript code". – xerx593 Commented Feb 18, 2019 at 18:36
  • 1 @MatsAndersson, Since you get the proper response when call this via the browser. The problem is in your front-end code. Can you add the full front end code? Also, do add the code for the other request too – Phenomenal One Commented Feb 25, 2019 at 10:32
  • 2 @MatsAndersson - If it works in the browser, your problem is not the backend. The moment the JSON goes on the wire, there's not type Guy, there's no "custom beans" and so on. The json is plain text and your JS frontend should know how to work with that. Can you add interceptors for the request and responses and paste the output on both? github./axios/axios#interceptors – hovanessyan Commented Mar 3, 2019 at 7:39
 |  Show 21 more ments

6 Answers 6

Reset to default 1

Could you please try changing the header to accept in the javascript

return dispatch => {
        dispatch(fetchMenuStart());
        const url = 'https://boot.ourpany.:8443/get/guy/1'; 
        const headers = {
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            }
        }
        axios.get(url, headers)
            .then(res => {
                console.log(res); 
                dispatch(fetchMenuSuccess(res.data.categories, res.data.restaurant));
            })
            .catch(error => {   
                console.log("error", error);
                const errorMsg = 'There was an error fetching the menu';
                dispatch(fetchMenuFail(errorMsg)); 
            });
    };

I have finally solved this issue by disabling CORS, with the following class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Profile("devel")
@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }

}

I have also added the @Profile annotation to disable CORS only on development time.

By the way, the reason for the issue seems to be explained in:

https://chromium.googlesource./chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md#Protecting-JSON

When returning an object, it is interpreted as a non-empty JSON object (such as {"key": "value"}). When returning a list, the same text is wrapped in squared brackets and it passes the protection.

If you use spring, you should use ResponseEntity instead of directly returning the object:

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

This is how I write my controllers:

@RestController
@RequestMapping(USERS)
public class UserController {

  @Autowired
  private UserService userService;

  @Autowired
  private RoleService roleService;

  @Autowired
  private LdapUserDetailsManager userDetailsService;

  @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
  public ResponseEntity<?> list(PagedResourcesAssembler<User> pageAssembler, @PageableDefault(size = 20) Pageable pageable, UserDTO condition) {
    Page<User> page = userService.findAll(pageable, condition);
    PagedResources<?> resources = pageAssembler.toResource(page, new UserResourceAssembler());
    return ResponseEntity.ok(resources);
  }

  @GetMapping(value = CoreHttpPathStore.PARAM_ID, produces= MediaType.APPLICATION_JSON_VALUE)
  public ResponseEntity<UserResource> get(@PathVariable("id") Long id) {
    User user = userService.get(id);
    UserResource resource = new UserResourceAssembler().toResource(user);
    return ResponseEntity.ok(resource);
  }

  private void preProcessEntity(@RequestBody UserDTO entity) {
    if (null != entity.getPassword()) {
      userDetailsService.changePassword(entity.getOldPassword(), entity.getPassword());
    }
  }

  @PostMapping
  @ResponseStatus(HttpStatus.CREATED)
  Long create(@RequestBody User user) {
    userService.insert(user);
    return user.getId();
  }

  @PutMapping(CoreHttpPathStore.PARAM_ID)
  @ResponseStatus(HttpStatus.NO_CONTENT)
  void modify(@PathVariable("id") Long id, @RequestBody UserDTO user) {
    user.setId(id);
    preProcessEntity(user);
    userService.updateIgnore(user);
  }

  @DeleteMapping(CoreHttpPathStore.PARAM_ID)
  @ResponseStatus(HttpStatus.NO_CONTENT)
  void delete(@PathVariable("id") Long id) {
    userService.delete(id);
  }

  @DeleteMapping
  @ResponseStatus(HttpStatus.NO_CONTENT)
  void bulkDelete(@RequestBody Long[] ids) {
    userService.delete(ids);
  }
}

As your javascript is on a different domain from the spring-boot service, you need to configure CORS.

This could be done globally adding @CrossOrigin like this :

@RestController
@CrossOrigin
public class GuyController {

OK everyone, thank you so much for your efforts. It turns out that the solution suggested by @mpromonet (adding CrossOrigin annotation on the controller) solves this problem. I am still very curious to know why a method returning List works and one returning a Guy does not if this is a cross-origin issue. It does not seem logical and it makes the issue a lot harder to figure out.

You have to add @ResponseBody annotation before your method.

@ResponseBody
public Guy ....
发布评论

评论列表(0)

  1. 暂无评论