Retornando objeto JSON como resposta no Spring Boot

88

Tenho um RestController de amostra no Spring Boot:

@RestController
@RequestMapping("/api")
class MyRestController
{
    @GetMapping(path = "/hello")
    public JSONObject sayHello()
    {
        return new JSONObject("{'aa':'bb'}");
    }
}

Estou usando a biblioteca JSON org.json

Quando eu clico na API /hello, recebo uma exceção dizendo:

Servlet.service () para servlet [dispatcherServlet] no contexto com o caminho [] lançou a exceção [Falha no processamento da solicitação; a exceção aninhada é java.lang.IllegalArgumentException: Nenhum conversor encontrado para o valor de retorno do tipo: class org.json.JSONObject] com causa raiz

java.lang.IllegalArgumentException: Nenhum conversor encontrado para o valor de retorno do tipo: class org.json.JSONObject

Qual é o problema? Alguém pode explicar o que exatamente está acontecendo?

Iwekesi
fonte
Jackson não pode converter JSONObject em json.
Pau
Ok, eu entendo isso. O que pode ser feito para corrigir isso?
iwekesi
1
Quero que a resposta seja construída na hora. Não quero criar classes específicas para cada resposta.
iwekesi
2
Pode ser melhor apenas ter seu método retornado como String. Além disso, você também pode anexar a anotação @ResponseBody ao método, isso tratará sua resposta conforme solicitado :-)@GetMapping(path = "/hello") @ResponseBody public String sayHello() {return"{'aa':'bb'}";}
vegaasen
@vegaasen você pode elaborar um pouco sobre ResponseBody
iwekesi

Respostas:

109

Como você está usando a Web do Spring Boot, a dependência de Jackson está implícita e não precisamos definir explicitamente. Você pode verificar a dependência de Jackson em seu pom.xmlna guia de hierarquia de dependência se estiver usando o eclipse.

E, como você anotou, @RestControllernão há necessidade de fazer uma conversão json explícita. Basta retornar um POJO e o serializador jackson cuidará da conversão para json. É equivalente a usar @ResponseBodyquando usado com @Controller. Em vez de colocar @ResponseBodyem cada método de controlador, colocamos em @RestControllervez de vanilla @Controllere, @ResponseBodypor padrão, é aplicado em todos os recursos nesse controlador.
Consulte este link: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-responsebody

O problema que você está enfrentando é porque o objeto retornado (JSONObject) não tem getter para certas propriedades. E sua intenção não é serializar este JSONObject, mas sim serializar um POJO. Então é só devolver o POJO.
Consulte este link: https://stackoverflow.com/a/35822500/5039001

Se você deseja retornar uma string serializada json, basta retornar a string. O Spring usará StringHttpMessageConverter em vez do conversor JSON neste caso.

prem kumar
fonte
se string json é o que você deseja retornar de java, então você pode simplesmente retornar uma string se já estiver serializada em json. Não há necessidade de converter string em json e json de volta em string.
prem kumar
6
Se você quiser retornar um conjunto de pares nome-valor sem uma estrutura rígida de tempo de compilação, você pode retornar um Map<String,Object>ou um Propertiesobjeto
Vihung
@prem kumar pergunta aleatória: o que você quer dizer com 'em vez de controlador vanilla e ResponseBody'? o que é baunilha aqui?
Orkun Ozen
eu quis dizer um controlador normal e com a anotação ResponseBody colocada em cada método de solicitação.
prem kumar
67

A razão pela qual sua abordagem atual não funciona é porque Jackson é usado por padrão para serializar e desserializar objetos. No entanto, ele não sabe como serializar o JSONObject. Se você deseja criar uma estrutura JSON dinâmica, pode usar um Map, por exemplo:

@GetMapping
public Map<String, String> sayHello() {
    HashMap<String, String> map = new HashMap<>();
    map.put("key", "value");
    map.put("foo", "bar");
    map.put("aa", "bb");
    return map;
}

Isso levará à seguinte resposta JSON:

{ "key": "value", "foo": "bar", "aa": "bb" }

Isso é um pouco limitado, pois pode se tornar um pouco mais difícil adicionar objetos filhos. Jackson tem seu próprio mecanismo, porém, usando ObjectNodee ArrayNode. Para usá-lo, você deve fazer o autowire ObjectMapperem seu serviço / controlador. Então você pode usar:

@GetMapping
public ObjectNode sayHello() {
    ObjectNode objectNode = mapper.createObjectNode();
    objectNode.put("key", "value");
    objectNode.put("foo", "bar");
    objectNode.put("number", 42);
    return objectNode;
}

Essa abordagem permite adicionar objetos filho, matrizes e usar todos os vários tipos.

g00glen00b
fonte
2
o que é mapper aqui?
iwekesi
1
@iwekesi esse é o Jackson que ObjectMappervocê deve autowire (veja o parágrafo acima do meu último trecho de código).
g00glen00b
1
É impressionante saber que é preciso ir tão longe para produzir objetos JSON significativos! Também é triste que a Pivotal não faça nenhum esforço ( spring.io/guides/gs/actuator-service ) para apontar essas limitações. Felizmente, temos ASSIM! ;)
cogitoergosum
@HikaruShindo java.util.HashMapé uma funcionalidade central do Java desde o Java 1.2.
g00glen00b
44

Você pode retornar uma resposta conforme Stringsugerido por @vagaasen ou pode usar o ResponseEntityobjeto fornecido pelo Spring conforme abaixo. Desta forma, você também pode retornar o Http status codeque é mais útil na chamada de serviço da web.

@RestController
@RequestMapping("/api")
public class MyRestController
{

    @GetMapping(path = "/hello", produces=MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> sayHello()
    {
         //Get data from service layer into entityList.

        List<JSONObject> entities = new ArrayList<JSONObject>();
        for (Entity n : entityList) {
            JSONObject entity = new JSONObject();
            entity.put("aa", "bb");
            entities.add(entity);
        }
        return new ResponseEntity<Object>(entities, HttpStatus.OK);
    }
}
Sangam Belose
fonte
1
Se eu adicionar JSONObject em entidades, novamente estou me dando uma exceção semelhante
iwekesi
@Sangam, sua resposta me ajudou em outro problema relacionado a jackson-dataformat-xml
divino
Isto foi uma grande ajuda! Obrigado!
jones-chris
1
Eu me pergunto por que essa resposta não foi mais votada. Eu também sou novo no Spring, então não sei se essa é uma boa prática de engenharia de software. Com isso dito, essa resposta realmente me ajudou. No entanto, tive muitos problemas com um JSONObject, mas como o Spring usa Jackson, mudei para um HashMape então o código que li nesta resposta funcionou.
Melvin Roest de
2
+1 por sugerir o MediaType.APPLICATION_JSON_VALUE, pois garante que o resultado produzido seja analisado como json e não xml, como pode acontecer se você não definir
Sandeep Mandori
11

você também pode usar um hashmap para isso

@GetMapping
public HashMap<String, Object> get() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("results", somePOJO);
    return map;
}
maximus
fonte
6
@RequestMapping("/api/status")
public Map doSomething()
{
    return Collections.singletonMap("status", myService.doSomething());
}

PS. Funciona apenas para 1 valor

Dmitry
fonte
3

usar ResponseEntity<ResponseBean>

Aqui você pode usar ResponseBean ou Any java bean conforme desejar para retornar sua resposta de API e essa é a melhor prática. Usei Enum como resposta. ele retornará o código de status e a mensagem de status da API.

@GetMapping(path = "/login")
public ResponseEntity<ServiceStatus> restApiExample(HttpServletRequest request,
            HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        loginService.login(username, password, request);
        return new ResponseEntity<ServiceStatus>(ServiceStatus.LOGIN_SUCCESS,
                HttpStatus.ACCEPTED);
    }

para resposta ServiceStatus ou (ResponseBody)

    public enum ServiceStatus {

    LOGIN_SUCCESS(0, "Login success"),

    private final int id;
    private final String message;

    //Enum constructor
    ServiceStatus(int id, String message) {
        this.id = id;
        this.message = message;
    }

    public int getId() {
        return id;
    }

    public String getMessage() {
        return message;
    }
}

Spring REST API deve ter a chave abaixo na resposta

  1. Código de Status
  2. mensagem

você receberá a resposta final abaixo

{

   "StatusCode" : "0",

   "Message":"Login success"

}

você pode usar ResponseBody (java POJO, ENUM, etc.) de acordo com sua necessidade.

Ved Prakash
fonte
2

Criação mais correta de DTO para consultas de API, por exemplo, entityDTO:

  1. Resposta padrão OK com lista de entidades:
@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.OK)
public List<EntityDto> getAll() {
    return entityService.getAllEntities();
}

Mas se você precisar retornar diferentes parâmetros de mapa, pode usar os próximos dois exemplos
2. Para retornar um parâmetro como mapa:

@GetMapping(produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getOneParameterMap() {
    return ResponseEntity.status(HttpStatus.CREATED).body(
            Collections.singletonMap("key", "value"));
}
  1. E se você precisar retornar o mapa de alguns parâmetros (desde Java 9):
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getSomeParameters() {
    return ResponseEntity.status(HttpStatus.OK).body(Map.of(
            "key-1", "value-1",
            "key-2", "value-2",
            "key-3", "value-3"));
}
Alexander Yushko
fonte
1

Se você precisar retornar um objeto JSON usando uma String, o seguinte deve funcionar:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.ResponseEntity;
...

@RestController
@RequestMapping("/student")
public class StudentController {

    @GetMapping
    @RequestMapping("/")
    public ResponseEntity<JsonNode> get() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode json = mapper.readTree("{\"id\": \"132\", \"name\": \"Alice\"}");
        return ResponseEntity.ok(json);
    }
    ...
}
Ashwin
fonte