JSON Pointer, Patch ও Merge Patch
সংক্ষেপে: তিনটি ছোট স্পেসিফিকেশন মিলে একটি পরিবর্তন প্রোটোকল: Pointer লোকেট করে, Patch সুনির্দিষ্টভাবে সম্পাদনা করে, Merge Patch আংশিক মার্জ করে। ভুল বাছাই অ্যারে ও সমবর্তীতায় ক্ষতি করে।

মূল কথা: অবস্থান দিয়ে লোকেট করতে JSON Pointer আর শর্ত দিয়ে ফিল্টার বা পুনরাবৃত্তি করতে JSONPath; অ্যারে ইনসার্ট/রিমুভ, null লেখা ও সংস্করণ দাবি JSON Patch-এ ছাড়ুন, আর Merge Patch রাখুন সমতল কনফিগারেশন ও ফর্ম রিসোর্সের জন্য।
লোকেট করা ও পরিবর্তন করা দুই ভিন্ন জিনিস, আর স্পেসিফিকেশন সেগুলো তিনটি নথিতে ভাগ করেছে। গুলিয়ে ফেলার সাধারণ দাম হলো "পুরো অ্যারে প্রতিস্থাপিত হয়ে গেছে" আর কেউ খেয়াল করে না।
১. JSON Pointer: সবচেয়ে ছোট লোকেটিং সিনট্যাক্স
RFC 6901 ঠিক একটি কাজ করে: একটি স্ট্রিং দিয়ে ডকুমেন্টের একটি নোডে ইশারা করা।
"" → the whole document
"/store" → the store member under the root
"/store/book" → one level deeper
"/store/book/0/title" → item 0 of the array
তিন নিয়ম:
- খালি স্ট্রিং রুট নোডেই ইশারা করে, "খালি নোড"-এ নয়;
- প্রতিটি
/একটি রেফারেন্স টোকেন আলাদা করে; অ্যারে দশমিক ইনডেক্স ব্যবহার করে (শুরুতে শূন্য নিষিদ্ধ, তাই01অবৈধ); - টোকেনের ভেতরে
~লিখতে হবে~0এবং/লিখতে হবে~1—/-এর আগে~প্রতিস্থাপন করুন, নাহলে দ্বিগুণ এস্কেপ হয়।
/**
* Escapes a single reference token into a JSON Pointer segment.
* @param token Original key name or index
* @returns Escaped token
*/
function escapePointerToken(token: string): string {
return token.replace(/~/g, '~0').replace(/\//g, '~1');
}
/**
* Joins an array of key names into a JSON Pointer.
* @param tokens Array of key names or indices
* @returns Pointer string beginning with /
*/
function buildPointer(tokens: Array<string | number>): string {
return tokens.map((token) => `/${escapePointerToken(String(token))}`).join('');
}
মূল্য সবচেয়ে ভালো দেখায় যখন কি নিজেই / ধারণ করে: {"a/b": 1}-এর পয়েন্টার /a~1b, /a/b নয় (পরেরটি মানে a-এর অধীনে b)।
URL-এর ভেতরে ব্যবহার করলে শতাংশ-এনকোডিংও দরকার, কারণ # ও ~-এর ফ্র্যাগমেন্টে বিশেষ অর্থ আছে।
২. JSON Patch: ছয় অপারেশনের পারমাণবিক ক্রম
RFC 6902 পরিবর্তন বর্ণনা করে অপারেশনের অ্যারে দিয়ে, মিডিয়া টাইপ application/json-patch+json:
[
{ "op": "test", "path": "/version", "value": 7 },
{ "op": "replace", "path": "/name", "value": "নতুন নাম" },
{ "op": "add", "path": "/tags/-", "value": "json" },
{ "op": "remove", "path": "/temp" },
{ "op": "copy", "from": "/name", "path": "/alias" },
{ "op": "move", "from": "/tags/0", "path": "/tags/1" }
]
| অপারেশন | সেমান্টিক্স | সীমানা |
|---|---|---|
add |
সদস্য যোগ করে বা অ্যারে এলিমেন্ট ঢোকায় | অ্যারে ইনডেক্স দৈর্ঘ্যের সমান হতে পারে (অ্যাপেন্ড); - মানে শেষ |
remove |
সরায় | পাথ অবশ্যই থাকতে হবে |
replace |
বিদ্যমান মান বদলায় | পাথ অবশ্যই থাকতে হবে (remove + add-এর সমান, কিন্তু আগে পাথ থাকা দরকার) |
move |
সরায় | নিজের উপ-পাথে নোড সরাতে পারবেন না |
copy |
কপি করে | উৎস অবশ্যই থাকতে হবে |
test |
সমতা দাবি করে | ব্যর্থ হলে পুরো প্যাচ প্রয়োগ হয় না; অপ্টিমিস্টিক লকিংয়ে ব্যবহৃত |
মূল বৈশিষ্ট্য পারমাণবিকতা: যেকোনো অপারেশন ব্যর্থ হলে পুরো প্যাচ রোলব্যাক হয়, তাই "প্রথম তিনটি প্রয়োগ হয়ে চতুর্থটি ব্যর্থ" এমন মধ্যবর্তী অবস্থা নেই।
এতে এটি চমৎকার PATCH রিকোয়েস্ট বডি।
২.১ অপ্টিমিস্টিক কনকারেন্সির জন্য test
[
{ "op": "test", "path": "/etag", "value": "a3f9c1" },
{ "op": "replace", "path": "/status", "value": "paid" }
]
ক্লায়েন্ট আগে GET করে etag নেয় ও test অপারেশনে লেখে। সমবর্তী লেখায় দ্বিতীয় রিকোয়েস্টের test ব্যর্থ হয় আর সার্ভার
409 Conflict বা 412 Precondition Failed ফেরত দেয়। এটি last-write-wins-এর চেয়ে অনেক নিরাপদ এবং কোনো ডিস্ট্রিবিউটেড লক দরকার করে না।
২.২ অ্যারেতে অপারেশন সবচেয়ে ভুলপ্রবণ
// Want to insert at index 1
{ "op": "add", "path": "/items/1", "value": {...} } // Correct: insert; the original index 1 and everything after shift back
// Want to replace index 1
{ "op": "replace", "path": "/items/1", "value": {...} } // Correct: replace
অ্যারে ইনডেক্স n-এ add একটি ইনসার্ট, ওভাররাইট নয়; রেঞ্জের বাইরের ইনডেক্স (n > length) এরর দেওয়া উচিত।
কিছু বাস্তবায়ন এতে শিথিল, ফলে একই প্যাচ সার্ভারভেদে আলাদা আচরণ করে।
৩. Merge Patch: লক্ষ্যের মতো দেখতে একটি খণ্ড
RFC 7386-এ কেবল চার নিয়ম:
- লক্ষ্য অবজেক্ট আর প্যাচ অবজেক্ট → পুনরাবৃত্তভাবে মার্জ;
- প্যাচে কোনো কি-এর মান
null→ লক্ষ্য থেকে সেই কি মুছুন; - প্যাচে কোনো কি-এর মান অবজেক্ট নয় → সরাসরি প্রতিস্থাপন;
- প্যাচ নিজেই অবজেক্ট নয় → পুরো লক্ষ্য প্রতিস্থাপিত।
// Target
{ "a": "b", "c": { "d": "e", "f": "g" } }
// Patch
{ "a": "z", "c": { "f": null } }
// Result
{ "a": "z", "c": { "d": "e" } }
মিডিয়া টাইপ application/merge-patch+json। এটি "আংশিক হালনাগাদ"-এর মতো পড়া যায়, যা ভুল ব্যবহার করা সহজ করে:
- অ্যারে কেবল সম্পূর্ণ প্রতিস্থাপনযোগ্য।
list[2]মুছতে চান? Merge Patch পারে না; nullলিখতে পারেন না।nullইতিমধ্যে মোছা অর্থে দখলকৃত;- "এই ফিল্ডটি ছুঁবেন না" ও "এই ফিল্ড মুছুন"-এর মধ্যে তৃতীয় অবস্থা প্রকাশ করতে পারে না (যেমন স্পষ্টভাবে খালি করা)।
৪. সক্ষমতা ম্যাট্রিক্স: কোনটি বাছবেন
| প্রয়োজন | JSON Patch | Merge Patch |
|---|---|---|
| নির্দিষ্ট অ্যারে এলিমেন্ট সুনির্দিষ্টভাবে বদলানো | ✓ | ✗ |
| অ্যারে থেকে একটি আইটেম মোছা | ✓ | ✗ |
| কয়েকটি ফিল্ড আংশিক হালনাগাদ | ✓ (একাধিক replace) | ✓ (সবচেয়ে সংক্ষিপ্ত) |
null মান লেখা |
✓ | ✗ |
| কনকারেন্সি কন্ট্রোল (সংস্করণ দাবি) | ✓ (test) | ✗ (কেবল If-Match দিয়ে) |
| প্যাচের পাঠযোগ্যতা | মধ্যম | ভালো (ডেটার মতো দেখায়) |
| বাস্তবায়ন জটিলতা | বেশি | কম |
| উপযুক্ত রিসোর্স আকৃতি | গভীর নেস্টেড, লিস্ট-সদৃশ | সমতল কনফিগারেশন, ফর্ম |
হাতের নিয়ম:
- ফর্ম, সেটিংস পেজ, কনফিগারেশন রিসোর্স → Merge Patch; ফ্রন্টএন্ড কেবল বদলানো ফিল্ড জমা দেয়;
- লিস্টে যোগ-বিয়োগ, স্টেট মেশিন রূপান্তর, কনকারেন্সি সুরক্ষা দরকার এমন সব → JSON Patch;
- দুটোই সহাবস্থান করতে পারে: আলাদা এন্ডপয়েন্ট আলাদা
Content-Typeঘোষণা করে, সার্ভার টাইপ অনুযায়ী ডিসপ্যাচ করে।
৫. বাস্তবায়নের নোট
import { applyPatch, compare } from 'fast-json-patch';
const document = { version: 7, items: [{ id: 1 }, { id: 2 }] };
const patch = compare(document, { version: 8, items: [{ id: 1 }] });
// test operations are validated by default; setting mutateDocument to true modifies in place, use with caution
const result = applyPatch(document, patch, true, false);
console.log(result.newDocument);
সহজে বাদ পড়া চারটি বিষয়:
- ভাগ করা অবজেক্ট জায়গায় বদলাবেন না; ব্যর্থ প্যাচ অর্ধ-পরিবর্তিত অবস্থা রেখে যায় (বাস্তবায়ন রোলব্যাক নিশ্চিত না করলে);
- পাথ যাচাই: বাইরের প্যাচে প্রতিটি
path/fromযাচাই করুন আর__proto__ওconstructor-এর মতো কি প্রত্যাখ্যান করুন; - আকার সীমা: প্যাচ এন্ট্রির সংখ্যা ক্যাপ করুন যাতে একটি প্যাচ লক্ষ লক্ষ অপারেশন না করে;
- অডিটিং: প্যাচ স্বাভাবিকভাবেই অডিট লগের উপযুক্ত — এরা "কী বদলেছে"-র সম্পূর্ণ বর্ণনা।
৬. এরর সেমান্টিক্স
| পরিস্থিতি | প্রস্তাবিত স্টেটাস কোড | ব্যাখ্যা |
|---|---|---|
| প্যাচ অ্যারে নয় / কাঠামোগতভাবে অবৈধ | 400 Bad Request |
রিকোয়েস্ট বডির ফরম্যাট ভুল |
path যে নোডে ইশারা করে তা নেই |
422 Unprocessable Entity |
সিনট্যাক্টিকভাবে বৈধ কিন্তু সেমান্টিকভাবে চালানো যায় না |
test অপারেশন ব্যর্থ |
409 বা 412 |
সংস্করণ সংঘর্ষ বোঝায় |
| সেই পাথ পরিবর্তনের অনুমতি নেই | 403 Forbidden |
রিসোর্সের অস্তিত্ব ইচ্ছাকৃতভাবে লুকাতে না চাইলে 404 দিয়ে ঢাকবেন না |
400 ও 422 আলাদা করা গুরুত্বপূর্ণ: প্রথমটি মানে "আপনার প্যাচ আমি বুঝতে পারি না", দ্বিতীয়টি মানে "বুঝেছি, কিন্তু এই ডেটা ওই পরিবর্তন অনুমোদন করে না"।
৭. চেকলিস্ট
- [ ] এন্ডপয়েন্ট সঠিক
Content-Typeঘোষণা করে (json-patch+json/merge-patch+json); - [ ] প্যাচ প্রয়োগ পারমাণবিক, ব্যর্থতায় কোনো আংশিক ফল থাকে না;
- [ ] বাইরের প্যাচ পাথ হোয়াইটলিস্ট ও এন্ট্রি-সংখ্যা সীমা দিয়ে যাচাই হয়;
- [ ] কনকারেন্সি কন্ট্রোলে
testবাIf-Matchব্যবহৃত; - [ ] প্যাচ অডিট লগে লেখা হয়;
- [ ] অ্যারে পরিস্থিতিতে স্পষ্টভাবে JSON Patch বাছাই; Merge Patch-এর উপর ভরসা নয়।
সাধারণ প্রশ্ন
JSON Patch ও Merge Patch-এর মধ্যে কীভাবে ঠিক করব?
নির্দিষ্ট অ্যারে এলিমেন্ট বদলানো বা সরানো, null মান লেখা, বা সংস্করণ দাবি দরকার হলে JSON Patch ব্যবহার করুন; যে ফর্ম ও কনফিগারেশন রিসোর্স কেবল কয়েকটি ফিল্ড হালনাগাদ করে সেখানে Merge Patch। Merge Patch একটি অ্যারে আইটেম মুছতে পারে না, null লিখতেও পারে না, কারণ null ইতিমধ্যে মোছা বোঝায়।
অ্যারে ইনডেক্স n-এ add ওভাররাইট নয় কেন?
কারণ add অ্যারেতে ঢোকায়: ইনডেক্স n-এর এলিমেন্ট ও তার পরের সব পিছনে সরে। বিদ্যমান এলিমেন্ট বদলাতে replace ব্যবহার করতে হবে, আর পাথ আগে থাকতে হবে। রেঞ্জের বাইরের ইনডেক্স (n > length) এরর দেওয়া উচিত; শিথিল বাস্তবায়ন একই প্যাচকে সার্ভারভেদে আলাদা আচরণ করায়।
মূল অবজেক্ট জায়গায় পরিবর্তন করা উচিত?
না। ব্যর্থ প্যাচ অর্ধ-পরিবর্তিত অবস্থা রেখে যায় যদি বাস্তবায়ন রোলব্যাক নিশ্চিত না করে। গভীর কপি বা structured clone-এ প্যাচ প্রয়োগ করুন, সফল হলে রেফারেন্স অদলবদল করুন।