Spring MVC - Cara mengembalikan String sederhana sebagai JSON di Rest Controller

146

Pertanyaan saya pada dasarnya adalah tindak lanjut dari pertanyaan ini .

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return "Hello World";
    }
}

Di atas, Spring akan menambahkan "Hello World" ke dalam isi respons. Bagaimana cara mengembalikan String sebagai respons JSON? Saya mengerti bahwa saya dapat menambahkan kutipan, tetapi itu lebih terasa seperti retasan.

Harap berikan contoh apa pun untuk membantu menjelaskan konsep ini.

Catatan: Saya tidak ingin ini ditulis langsung ke badan Respons HTTP, saya ingin mengembalikan String dalam format JSON (Saya menggunakan Pengontrol saya dengan RestyGWT yang memerlukan respons dalam format JSON yang valid).

Belati Arena Gilbert
sumber
Anda dapat mengembalikan Peta atau objek / entitas apa pun yang berisi string Anda
Denys Denysiuk
Jadi maksud Anda Anda ingin nilai String diserialkan ke string JSON?
Sotirios Delimanolis

Jawaban:

164

Baik kembali text/plain(seperti dalam Pesan string hanya mengembalikan dari Spring MVC 3 Controller ) ATAU bungkus String Anda adalah beberapa objek

public class StringResponse {

    private String response;

    public StringResponse(String s) { 
       this.response = s;
    }

    // get/set omitted...
}


Setel jenis tanggapan Anda ke MediaType.APPLICATION_JSON_VALUE(= "application/json")

@RequestMapping(value = "/getString", method = RequestMethod.GET,
                produces = MediaType.APPLICATION_JSON_VALUE)

dan Anda akan memiliki JSON yang terlihat seperti itu

{  "response" : "your string value" }
Shaun
sumber
126
Anda juga bisa kembali Collections.singletonMap("response", "your string value")untuk mendapatkan hasil yang sama tanpa harus membuat kelas pembungkus.
Bohuslav Burghardt
@Bohuslav Itu tip yang bagus.
Shaun
7
Tidak benar bahwa itu membutuhkan kunci dan nilai. String tunggal atau larik string adalah JSON yang valid. Jika Anda tidak setuju mungkin Anda bisa menjelaskan mengapa situs web jsonlint menerima keduanya sebagai JSON yang valid.
KyleM
2
bagaimana kelas pembungkus diubah menjadi JSON?
Rocky Inde
3
Saya pikir itu cukup untuk kembaliCollections.singleton("your string value")
gauee
58

JSON pada dasarnya adalah String dalam konteks PHP atau JAVA. Itu berarti string yang merupakan JSON valid dapat dikembalikan sebagai respons. Mengikuti harus bekerja.

  @RequestMapping(value="/user/addUser", method=RequestMethod.POST)
  @ResponseBody
  public String addUser(@ModelAttribute("user") User user) {

    if (user != null) {
      logger.info("Inside addIssuer, adding: " + user.toString());
    } else {
      logger.info("Inside addIssuer...");
    }
    users.put(user.getUsername(), user);
    return "{\"success\":1}";
  }

Ini tidak masalah untuk respon string sederhana. Tetapi untuk respon JSON yang kompleks, Anda harus menggunakan kelas pembungkus seperti yang dijelaskan oleh Shaun.

vansia pinkal
sumber
8
Ini harus menjadi jawaban yang diterima, karena ini adalah jawaban yang tepat untuk pertanyaan OP.
SRy
Terima kasih, @ResponseBody adalah yang saya butuhkan
riskop
Penasaran mana posisi "lebih baik" untuk @ResponseBody sebelum atau sesudah kata kunci publik? Saya selalu meletakkannya setelah itu, karena lebih diidentifikasikan dengan nilai pengembalian.
David Bradley
Tapi saya mendapat garis miring melarikan diri ini sebagai tanggapan saya. Hal
Ivan
28

Dalam satu proyek kami menangani ini menggunakan JSONObject ( info ketergantungan maven ). Kami memilih ini karena kami lebih suka mengembalikan String sederhana daripada objek pembungkus. Kelas pembantu internal dapat dengan mudah digunakan jika Anda tidak ingin menambahkan dependensi baru.

Contoh Penggunaan:

@RestController
public class TestController
{
    @RequestMapping("/getString")
    public String getString()
    {
        return JSONObject.quote("Hello World");
    }
}
Belati Arena Gilbert
sumber
1
Mungkin Anda harus menyebutkan dalam jawaban Anda, itu "\"Hello World\""akan bekerja dengan baik tanpa ketergantungan ekstra - itu yang JSONObject.quote()dilakukan, bukan?
jerico
Saya tidak suka solusinya, tetapi berhasil untuk saya. :-)
Michael Hegner
23

Anda dapat dengan mudah kembali JSONdengan Stringproperti responsesebagai berikut

@RestController
public class TestController {
    @RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
    public Map getString() {
        return Collections.singletonMap("response", "Hello World");
    }
}
Javasick
sumber
2
setiap kali Anda menggunakan '@RestController', Anda tidak perlu menggunakan '@ResponseBody'
jitendra varshney
13

Cukup batalkan pendaftaran StringHttpMessageConverterinstance default :

@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
  /**
   * Unregister the default {@link StringHttpMessageConverter} as we want Strings
   * to be handled by the JSON converter.
   *
   * @param converters List of already configured converters
   * @see WebMvcConfigurationSupport#addDefaultHttpMessageConverters(List)
   */
  @Override
  protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.stream()
      .filter(c -> c instanceof StringHttpMessageConverter)
      .findFirst().ifPresent(converters::remove);
  }
}

Diuji dengan metode penangan tindakan pengontrol dan penangan pengecualian pengontrol:

@RequestMapping("/foo")
public String produceFoo() {
  return "foo";
}

@ExceptionHandler(FooApiException.class)
public String fooException(HttpServletRequest request, Throwable e) {
  return e.getMessage();
}

Catatan akhir:

  • extendMessageConverterstersedia sejak Spring 4.1.3, jika berjalan pada versi sebelumnya Anda dapat menerapkan teknik yang sama configureMessageConverters, hanya membutuhkan sedikit lebih banyak pekerjaan.
  • Ini adalah salah satu pendekatan dari banyak kemungkinan pendekatan lainnya, jika aplikasi Anda hanya mengembalikan JSON dan tidak ada tipe konten lainnya, Anda lebih baik melewatkan konverter default dan menambahkan konverter jackson tunggal. Pendekatan lain adalah dengan menambahkan konverter default tetapi dalam urutan yang berbeda sehingga konverter jackson sebelum string satu. Ini harus memungkinkan metode tindakan pengontrol untuk menentukan bagaimana mereka ingin String dikonversi tergantung pada jenis media dari respons.
Amr Mostafa
sumber
1
Alangkah baiknya memiliki kode contoh terkait catatan terakhir ke-2 Anda.
Tony Baguette
1
converters.removeIf(c -> c instanceof StringHttpMessageConverter)
chrylis -cautiouslyoptimistic-
13

Saya tahu bahwa pertanyaan ini sudah lama tetapi saya ingin berkontribusi juga:

Perbedaan utama antara respons lainnya adalah pengembalian hashmap.

@GetMapping("...")
@ResponseBody
public Map<String, Object> endPointExample(...) {

    Map<String, Object> rtn = new LinkedHashMap<>();

    rtn.put("pic", image);
    rtn.put("potato", "King Potato");

    return rtn;

}

Ini akan mengembalikan:

{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Brenno Leal
sumber
7

Menambahkan produces = "application/json"dalam @RequestMappingpenjelasan seperti:

@RequestMapping(value = "api/login", method = RequestMethod.GET, produces = "application/json")

Petunjuk: Sebagai nilai kembali, saya sarankan untuk menggunakan ResponseEntity<List<T>>tipe. Karena data yang dihasilkan dalam tubuh JSON harus berupa larik atau objek sesuai dengan spesifikasinya, bukan satu string sederhana . Kadang-kadang dapat menyebabkan masalah (misalnya Observables in Angular2).

Perbedaan:

dikembalikan Stringsebagai json:"example"

dikembalikan List<String>sebagai json:["example"]

Aybars Yuksel
sumber
7

Sederhanakan:

    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        LOG.info("REST request health check");
        return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
    }
samarone
sumber
Menggunakan ResponseEntity tampaknya merupakan seni bagi saya. +1
Alexander
3

Tambahkan @ResponseBodyanotasi, yang akan menulis data yang dikembalikan dalam aliran keluaran.

hugo
sumber
1
ini tidak berhasil untuk saya. Saya punya@PostMapping(value = "/some-url", produces = APPLICATION_JSON_UTF8_VALUE)
aliopi
0

Masalah ini membuat saya marah: Spring adalah alat yang ampuh, namun, hal sederhana seperti menulis String keluaran seperti JSON tampaknya tidak mungkin tanpa peretasan yang buruk.

Solusi saya (di Kotlin) yang menurut saya paling tidak mengganggu dan paling transparan adalah dengan menggunakan saran pengontrol dan memeriksa apakah permintaan tersebut mengarah ke serangkaian titik akhir tertentu (REST API biasanya karena kami paling sering ingin mengembalikan SEMUA jawaban dari sini sebagai JSON dan tidak membuat spesialisasi di frontend berdasarkan apakah data yang dikembalikan adalah string biasa ("Jangan lakukan deserialisasi JSON!") atau yang lain ("Lakukan deserialisasi JSON!")). Aspek positif dari ini adalah bahwa pengontrol tetap sama dan tanpa peretasan.

The supportsMetode memastikan bahwa semua permintaan yang ditangani oleh StringHttpMessageConverter(misalnya konverter yang menangani output dari semua kontroler yang kembali string polos) diproses dan dalam beforeBodyWritemetode, kita mengendalikan di mana kasus kita ingin menyela dan mengkonversi output ke JSON (dan ubah tajuk sesuai).

@ControllerAdvice
class StringToJsonAdvice(val ob: ObjectMapper) : ResponseBodyAdvice<Any?> {
    
    override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean =
        converterType === StringHttpMessageConverter::class.java

    override fun beforeBodyWrite(
        body: Any?,
        returnType: MethodParameter,
        selectedContentType: MediaType,
        selectedConverterType: Class<out HttpMessageConverter<*>>,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    ): Any? {
        return if (request.uri.path.contains("api")) {
            response.getHeaders().contentType = MediaType.APPLICATION_JSON
            ob.writeValueAsString(body)
        } else body
    }
}

Saya berharap kedepannya kita akan mendapatkan anotasi sederhana dimana kita dapat mengganti yang HttpMessageConverterseharusnya digunakan untuk keluaran.

refleks cepat
sumber
-5

Tambahkan anotasi ini ke metode Anda

@RequestMapping(value = "/getString", method = RequestMethod.GET, produces = "application/json")
Dali
sumber