JSON Toolkit

JSON Pointer, Patch ও Merge Patch

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

মধ্যবর্তী~12 মিনিট পড়াপ্রকাশ 2026-10-30আপডেট 2026-10-30
JSON 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

তিন নিয়ম:

  1. খালি স্ট্রিং রুট নোডেই ইশারা করে, "খালি নোড"-এ নয়;
  2. প্রতিটি / একটি রেফারেন্স টোকেন আলাদা করে; অ্যারে দশমিক ইনডেক্স ব্যবহার করে (শুরুতে শূন্য নিষিদ্ধ, তাই 01 অবৈধ);
  3. টোকেনের ভেতরে ~ লিখতে হবে ~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-এ কেবল চার নিয়ম:

  1. লক্ষ্য অবজেক্ট আর প্যাচ অবজেক্ট → পুনরাবৃত্তভাবে মার্জ;
  2. প্যাচে কোনো কি-এর মান null → লক্ষ্য থেকে সেই কি মুছুন;
  3. প্যাচে কোনো কি-এর মান অবজেক্ট নয় → সরাসরি প্রতিস্থাপন;
  4. প্যাচ নিজেই অবজেক্ট নয় → পুরো লক্ষ্য প্রতিস্থাপিত।
// 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);

সহজে বাদ পড়া চারটি বিষয়:

  1. ভাগ করা অবজেক্ট জায়গায় বদলাবেন না; ব্যর্থ প্যাচ অর্ধ-পরিবর্তিত অবস্থা রেখে যায় (বাস্তবায়ন রোলব্যাক নিশ্চিত না করলে);
  2. পাথ যাচাই: বাইরের প্যাচে প্রতিটি path/from যাচাই করুন আর __proto__ ও constructor-এর মতো কি প্রত্যাখ্যান করুন;
  3. আকার সীমা: প্যাচ এন্ট্রির সংখ্যা ক্যাপ করুন যাতে একটি প্যাচ লক্ষ লক্ষ অপারেশন না করে;
  4. অডিটিং: প্যাচ স্বাভাবিকভাবেই অডিট লগের উপযুক্ত — এরা "কী বদলেছে"-র সম্পূর্ণ বর্ণনা।

৬. এরর সেমান্টিক্স

পরিস্থিতি প্রস্তাবিত স্টেটাস কোড ব্যাখ্যা
প্যাচ অ্যারে নয় / কাঠামোগতভাবে অবৈধ 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-এ প্যাচ প্রয়োগ করুন, সফল হলে রেফারেন্স অদলবদল করুন।