Menggunakan JSON Patch di Spring REST API

1. Pengenalan

Dari pelbagai kaedah HTTP yang ada, kaedah HTTP PATCH memainkan peranan yang unik. Ini membolehkan kita menerapkan kemas kini separa ke sumber HTTP.

Dalam tutorial ini, kita akan melihat bagaimana menggunakan kaedah HTTP PATCH bersama dengan format dokumen JSON Patch untuk menerapkan kemas kini separa ke sumber RESTful kami.

2. Kes Penggunaan

Mari mulakan dengan mempertimbangkan contoh sumber Pelanggan HTTP yang diwakili oleh dokumen JSON:

{ "id":"1", "telephone":"001-555-1234", "favorites":["Milk","Eggs"], "communicationPreferences": {"post":true, "email":true} }

Mari kita anggap bahawa nombor telefon pelanggan initelah berubah dan pelanggan menambahkan item baru ke dalam senarai produk kegemaran mereka. Ini bermakna bahawa kita perlu maklumat hanya telefon dan kegemaran bidang yang Pelanggan .

Bagaimana kita akan melakukannya?

Kaedah HTTP PUT yang popular terlintas di fikiran terlebih dahulu. Namun, kerana PUT menggantikan sumber sepenuhnya, itu bukan kaedah yang sesuai untuk menerapkan kemas kini separa dengan elegan. Lebih-lebih lagi, pelanggan harus melakukan GET sebelum kemas kini diterapkan dan disimpan.

Di sinilah kaedah HTTP PATCH sangat berguna.

Mari fahami kaedah HTTP PATCH dan format Patch JSON.

3. Kaedah HTTP PATCH dan Format Patch JSON

Kaedah HTTP PATCH menawarkan cara yang baik untuk menerapkan kemas kini separa ke sumber. Akibatnya, pelanggan hanya perlu menghantar perbezaan dalam permintaan mereka.

Mari lihat contoh ringkas permintaan HTTP PATCH:

PATCH /customers/1234 HTTP/1.1 Host: www.example.com Content-Type: application/example If-Match: "e0023aa4e" Content-Length: 100 [description of changes]

Badan permintaan HTTP PATCH menerangkan bagaimana sumber sasaran harus diubah untuk menghasilkan versi baru. Selanjutnya, format yang digunakan untuk mewakili [keterangan perubahan] berbeza bergantung pada jenis sumber. Untuk jenis sumber JSON, format yang digunakan untuk menggambarkan perubahan adalah JSON Patch.

Ringkasnya, format JSON Patch menggunakan "rangkaian operasi" untuk menerangkan bagaimana sumber sasaran harus diubah. Dokumen JSON Patch adalah susunan objek JSON. Setiap objek dalam array mewakili tepat satu operasi Patch JSON.

Sekarang mari kita lihat operasi Patch JSON bersama dengan beberapa contoh.

4. Operasi Patch JSON

Operasi JSON Patch diwakili oleh objek op tunggal .

Sebagai contoh, di sini kita menentukan operasi patch JSON untuk mengemas kini nombor telefon pelanggan:

{ "op":"replace", "path":"/telephone", "value":"001-555-5678" }

Setiap operasi mesti mempunyai satu anggota jalan . Juga, beberapa objek operasi mesti mengandungi anggota dari juga. Nilai jalan dan dari ahli adalah Penunjuk JSON. Ini merujuk pada lokasi dalam dokumen sasaran. Lokasi ini dapat mengarah ke kunci tertentu atau elemen array dalam objek sasaran.

Mari kita lihat secara ringkas operasi JSON Patch yang ada.

4.1. The add Operasi

Kami menggunakan operasi tambah untuk menambahkan anggota baru ke objek. Kita juga dapat menggunakannya untuk mengemas kini anggota yang ada dan memasukkan nilai baru ke dalam array pada indeks yang ditentukan.

Sebagai contoh, mari kita tambahkan "Roti" ke senarai kegemaran pelanggan di indeks 0:

{ "op":"add", "path":"/favorites/0", "value":"Bread" }

Maklumat pelanggan yang diubah selepas operasi tambah adalah:

{ "id":"1", "telephone":"001-555-1234", "favorites":["Bread","Milk","Eggs"], "communicationPreferences": {"post":true, "email":true} }

4.2. The keluarkan Operasi

The keluarkan operasi membuang nilai di lokasi sasaran. Selain itu, ia dapat menghapus elemen dari array pada indeks yang ditentukan.

Sebagai contoh, mari kita hapus komunikasi untuk pelanggan kami:

{ "op":"remove", "path":"/communicationPreferences" }

Butir-butir pelanggan diubahsuai selepas mengeluarkan operasi akan menjadi:

{ "id":"1", "telephone":"001-555-1234", "favorites":["Bread","Milk","Eggs"], "communicationPreferences":null }

4.3. Yang menggantikan Operasi

Yang menggantikan kemas kini operasi nilai di lokasi sasaran dengan nilai baru.

Sebagai contoh, mari kita kemas kini nombor telefon untuk pelanggan kami:

{ "op":"replace", "path":"/telephone", "value":"001-555-5678" }

Maklumat pelanggan yang diubah selepas operasi penggantian adalah:

{ "id":"1", "telephone":"001-555-5678", "favorites":["Bread","Milk","Eggs"], "communicationPreferences":null }

4.4. Yang bergerak Operasi

The langkah operasi membuang nilai di lokasi yang dinyatakan dan menambah ke lokasi sasaran.

Sebagai contoh, mari kita pindahkan "Roti" dari bahagian atas senarai kegemaran pelanggan ke bahagian bawah senarai:

{ "op":"move", "from":"/favorites/0", "path":"/favorites/-" }

Maklumat pelanggan yang diubah setelah operasi bergerak adalah:

{ "id":"1", "telephone":"001-555-5678", "favorites":["Milk","Eggs","Bread"], "communicationPreferences":null } 

The / favites / 0 dan / favourites / - dalam contoh di atas adalah JSON menunjukkan indeks permulaan dan akhir dari array kegemaran .

4.5. The salinan Operasi

The salinan salinan operasi nilai di lokasi yang ditetapkan ke lokasi sasaran.

Sebagai contoh, mari kita pendua "Susu" dalam senarai kegemaran :

{ "op":"copy", "from":"/favorites/0", "path":"/favorites/-" }

Maklumat pelanggan yang diubah selepas operasi penyalinan adalah:

{ "id":"1", "telephone":"001-555-5678", "favorites":["Milk","Eggs","Bread","Milk"], "communicationPreferences":null }

4.6. The ujian Operasi

The ujian ujian operasi yang nilai pada "jalan" sama dengan "nilai". Oleh kerana operasi PATCH bersifat atomik, PATCH harus dibuang jika mana-mana operasinya gagal. The ujian operasi boleh digunakan untuk validate bahawa prasyarat dan pasca-syarat telah dipenuhi.

Sebagai contoh, mari kita uji bahawa kemas kini ke bidang telefon pelanggan telah berjaya:

{ "op":"test", "path":"/telephone", "value":"001-555-5678" } 

Sekarang mari kita lihat bagaimana kita dapat menerapkan konsep di atas untuk contoh kita.

5. Permintaan HTTP PATCH Menggunakan Format Patch JSON

Kami akan melihat semula kes penggunaan Pelanggan kami .

Berikut adalah permintaan HTTP PATCH untuk melakukan kemas kini sebahagian ke senarai telefon dan kegemaran pelanggan menggunakan format JSON Patch:

curl -i -X PATCH //localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[ {"op":"replace","path":"/telephone","value":"+1-555-56"}, {"op":"add","path":"/favorites/0","value":"Bread"} ]' 

Yang paling penting, Content-Type untuk permintaan JSON Patch adalah application / json-patch + json . Juga, badan permintaan adalah pelbagai objek operasi JSON Patch:

[ {"op":"replace","path":"/telephone","value":"+1-555-56"}, {"op":"add","path":"/favorites/0","value":"Bread"} ]

Bagaimana kita memproses permintaan seperti itu di sisi pelayan?

Salah satu cara adalah dengan menulis kerangka khusus yang menilai operasi secara berurutan dan menerapkannya ke sumber sasaran sebagai unit atom. Jelas, pendekatan ini terdengar rumit. Juga, ini boleh menyebabkan cara penggunaan dokumen patch yang tidak standard.

Nasib baik, kita tidak perlu membuat sendiri pemprosesan permintaan JSON Patch.

API Java untuk Pemrosesan JSON 1.0, atau JSON-P 1.0, yang didefinisikan awalnya dalam JSR 353, memperkenalkan sokongan untuk Patch JSON di JSR 374. API JSON-P menyediakan jenis JsonPatch untuk mewakili pelaksanaan Patch JSON.

Walau bagaimanapun, JSON-P hanyalah API. Untuk bekerjasama dengan JSON-P API, kita perlu menggunakan perpustakaan yang menerapkannya. Kami akan menggunakan satu pustaka yang disebut json-patch untuk contoh dalam artikel ini.

Let's now look at how we can build a REST service that consumes HTTP PATCH requests using the JSON Patch format described above.

6. Implementing JSON Patch in a Spring Boot Application

6.1. Dependencies

The latest version of json-patch can be found from the Maven Central repository.

To begin with, let's add the dependencies to the pom.xml:

 com.github.java-json-tools json-patch 1.12 

Now, let's define a schema class to represent the Customer JSON document :

public class Customer { private String id; private String telephone; private List favorites; private Map communicationPreferences; // standard getters and setters }

Next, we'll look at our controller method.

6.2. The REST Controller Method

Then, we can implement HTTP PATCH for our customer use case:

@PatchMapping(path = "/{id}", consumes = "application/json-patch+json") public ResponseEntity updateCustomer(@PathVariable String id, @RequestBody JsonPatch patch) { try { Customer customer = customerService.findCustomer(id).orElseThrow(CustomerNotFoundException::new); Customer customerPatched = applyPatchToCustomer(patch, customer); customerService.updateCustomer(customerPatched); return ResponseEntity.ok(customerPatched); } catch (JsonPatchException | JsonProcessingException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } catch (CustomerNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } } 

Let's now understand what is going on in this method:

  • To begin with, we use the @PatchMapping annotation to mark the method as a PATCH handler method
  • When a patch request with the application/json-patch+json “Content-Type” arrives, Spring Boot uses the default MappingJackson2HttpMessageConverter to convert the request payload to a JsonPatch instance. As a result, our controller method will receive the request body as a JsonPatch instance

Within the method:

  1. First, we call the customerService.findCustomer(id) method to find the customer record
  2. Subsequently, if the customer record is found, we invoke the applyPatchToCustomer(patch, customer) method. This applies the JsonPatch to the customer (more on this later)
  3. We then invoke the customerService.updateCustomer(customerPatched) to save the customer record
  4. Finally, we return a 200 OK response to the client with the patched Customer details in the response

Most importantly, the real magic happens in the applyPatchToCustomer(patch, customer) method:

private Customer applyPatchToCustomer( JsonPatch patch, Customer targetCustomer) throws JsonPatchException, JsonProcessingException { JsonNode patched = patch.apply(objectMapper.convertValue(targetCustomer, JsonNode.class)); return objectMapper.treeToValue(patched, Customer.class); } 
  1. To begin with, we have our JsonPatch instance that holds the list of operations to be applied to the target Customer
  2. We then convert the target Customer into an instance of com.fasterxml.jackson.databind.JsonNode and pass it to the JsonPatch.apply method to apply the patch. Behind the scenes, the JsonPatch.apply deals with applying the operations to the target. The result of the patch is also a com.fasterxml.jackson.databind.JsonNode instance
  3. We then call the objectMapper.treeToValue method, which binds the data in the patched com.fasterxml.jackson.databind.JsonNode to the Customer type. This is our patched Customer instance
  4. Finally, we return the patched Customer instance

Let's now run some tests against our API.

6.3. Testing

To begin with, let's create a customer using a POST request to our API:

curl -i -X POST //localhost:8080/customers -H "Content-Type: application/json" -d '{"telephone":"+1-555-12","favorites":["Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}' 

We receive a 201 Created response:

HTTP/1.1 201 Location: //localhost:8080/customers/1 

The Location response header is set to the location of the new resource. It indicates that the id of the new Customer is 1.

Next, let's request a partial update to this customer using a PATCH request:

curl -i -X PATCH //localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[ {"op":"replace","path":"/telephone","value":"+1-555-56"}, {"op":"add","path":"/favorites/0","value": "Bread"} ]'

We receive a 200OK response with the patched customer details:

HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Fri, 14 Feb 2020 21:23:14 GMT {"id":"1","telephone":"+1-555-56","favorites":["Bread","Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}

7. Conclusion

In this article, we looked at how to implement JSON Patch in Spring REST APIs.

To begin with, we looked at the HTTP PATCH method and its ability to perform partial updates.

We then looked into what is JSON Patch and understood the various JSON Patch operations.

Akhirnya, kami membincangkan bagaimana menangani permintaan HTTP PATCH dalam aplikasi Spring Boot menggunakan perpustakaan json-patch.

Seperti biasa, kod sumber untuk contoh yang digunakan dalam artikel ini terdapat di GitHub.