مقایسه دو JSON زمانی لازم میشود که بخواهیم بفهمیم میان دو پاسخ API، دو فایل تنظیمات، دو نسخه از داده یا دو خروجی سیستم دقیقاً چه چیزی تغییر کرده است. مقایسه متنی ساده اغلب برای JSON مناسب نیست، چون تغییر تورفتگی، ترتیب کلیدها یا فاصلهها میتواند تفاوتهای ظاهری زیادی ایجاد کند؛ در حالی که داده از نظر ساختاری همان باشد. JSON Diff باید ساختار را Parse کند و تغییرها را در سطح کلید، مقدار، نوع داده، Object و Array نشان دهد.
پاسخ سریع: JSON Diff چیست؟
JSON Diff روشی برای مقایسه ساختاری دو سند JSON است. این روش مشخص میکند چه کلیدهایی اضافه یا حذف شدهاند، کدام مقدارها تغییر کردهاند، نوع داده کجا عوض شده و چه تفاوتی در Arrayها وجود دارد. یک ابزار درست باید تفاوتهای واقعی داده را از تغییرهای صرفاً ظاهری مانند فاصلهگذاری و تورفتگی جدا کند.
چرا مقایسه متنی برای JSON کافی نیست؟
JSON یک قالب ساختاری است، نه فقط متن. دو سند زیر از نظر ظاهر متفاوتاند، اما پس از Parse داده یکسانی تولید میکنند:
{"name":"xDFlow","active":true}{
"active": true,
"name": "xDFlow"
}مقایسه خطبهخط ممکن است کل فایل را تغییرکرده نشان دهد، زیرا ترتیب خطوط و فاصلهها فرق دارند. JSON Diff ساختاری ابتدا داده را Parse میکند و سپس کلیدها و مقدارها را مقایسه میکند. در نتیجه این دو نمونه معمولاً برابر گزارش میشوند، مگر اینکه ابزار عمداً ترتیب کلیدهای Object را نیز بخشی از مقایسه بداند.
انواع تفاوت در JSON
افزودهشدن کلید
// قبل
{"name":"Sara"}
// بعد
{"name":"Sara","role":"admin"}کلید role اضافه شده است. ابزار باید مسیر تغییر، مقدار جدید و نوع داده را نمایش دهد.
حذفشدن کلید
// قبل
{"id":10,"active":true}
// بعد
{"id":10}حذف کلید با تبدیل مقدار به null یکی نیست. در حالت اول کلید وجود ندارد؛ در حالت دوم کلید وجود دارد و مقدار آن null است.
تغییر مقدار
// قبل
{"status":"draft"}
// بعد
{"status":"published"}تغییر نوع داده
// قبل
{"count":"12"}
// بعد
{"count":12}گرچه مقدار ظاهری مشابه است، String با Number متفاوت است و میتواند رفتار اعتبارسنجی، مرتبسازی و محاسبه را تغییر دهد.
تغییر ساختار
// قبل
{"user":"Nima"}
// بعد
{"user":{"name":"Nima"}}در اینجا فقط مقدار عوض نشده؛ مدل داده از String به Object تبدیل شده است.
مقایسه Objectها چگونه انجام میشود؟
Object مجموعهای از Propertyهاست. مقایسه ساختاری معمولاً بر اساس نام کلید انجام میشود و ترتیب نوشتن Propertyها نباید معنای داده را تغییر دهد. الگوریتم معمول این مراحل را دارد:
- کلیدهای هر دو Object استخراج میشوند.
- کلیدهای مشترک، حذفشده و اضافهشده مشخص میشوند.
- مقدار کلیدهای مشترک بهصورت بازگشتی مقایسه میشود.
- مسیر هر تفاوت ثبت میشود.
اگر مقدار یک کلید Object یا Array باشد، مقایسه در همان شاخه ادامه پیدا میکند.
مقایسه Arrayها؛ بخش پیچیده JSON Diff
Array مرتب است و Index اعضا معنا دارد. بنابراین جابهجایی یک عضو میتواند به شکل حذف در Index قدیمی و افزودن در Index جدید دیده شود. ابزارهای Diff ممکن است سه رویکرد داشته باشند:
| روش | رفتار | کاربرد |
|---|---|---|
| مقایسه بر اساس Index | عضو صفر با عضو صفر مقایسه میشود | Arrayهایی که ترتیب دقیق مهم است |
| مقایسه بر اساس شناسه | اعضا با کلیدی مانند id تطبیق داده میشوند | فهرست موجودیتها |
| مقایسه بهصورت Set | ترتیب نادیده گرفته میشود | فهرستهایی که فقط عضویت مهم است |
هیچ روش واحدی برای همه Arrayها درست نیست. قبل از تفسیر نتیجه باید بدانید ابزار از کدام مدل استفاده میکند.
مثال جابهجایی عضو Array
// قبل
["red","green","blue"]
// بعد
["green","red","blue"]در مقایسه Indexمحور، دو موقعیت اول تغییرکردهاند. در مقایسه Setمحور، هیچ تفاوتی وجود ندارد. در یک الگوریتم تشخیص Move، ممکن است فقط جابهجایی گزارش شود.
مسیر تغییرها چگونه نمایش داده میشود؟
برای مشخصکردن محل تغییر، ابزارها از Path استفاده میکنند. مسیر ممکن است با Dot Notation، JSONPath یا JSON Pointer نمایش داده شود.
user.address.city
$.user.address.city
/user/address/cityهنگام استفاده از خروجی Diff در کد یا Patch باید قالب مسیر را دقیق بدانید؛ این قالبها یکسان نیستند.
JSON Diff و JSON Patch چه تفاوتی دارند؟
JSON Diff نتیجه مقایسه است؛ JSON Patch قالبی برای بیان مجموعه عملیات تغییر مانند افزودن، حذف و جایگزینی است. یک ابزار Diff ممکن است خروجی قابل خواندن برای انسان بدهد یا از روی تفاوتها عملیات Patch تولید کند.
| موضوع | JSON Diff | JSON Patch |
|---|---|---|
| هدف | پیداکردن تفاوتها | اعمال تغییرها |
| ورودی | دو سند JSON | یک سند و فهرست عملیات |
| خروجی | گزارش تفاوت | سند تغییرکرده |
| مناسب برای | بررسی و Debug | همگامسازی و Update |
مراحل مقایسه دو JSON
- هر دو ورودی را جداگانه با JSON Parser یا Validator بررسی کنید.
- مشخص کنید مقایسه ساختاری است یا متنی.
- برای Arrayها مدل مقایسه را تعیین کنید.
- در صورت نیاز گزینه نادیدهگرفتن ترتیب کلیدهای Object را فعال کنید.
- نتیجه را بر اساس Path مرور کنید.
- تغییر نوع داده را از تغییر مقدار جدا بررسی کنید.
- تفاوتهای مورد انتظار و ناخواسته را دستهبندی کنید.
- برای تغییرهای حساس، نتیجه را با Schema و تستهای برنامه تأیید کنید.
سناریوهای کاربردی
- مقایسه پاسخ API قبل و بعد از انتشار نسخه جدید
- بررسی تغییر فایل تنظیمات
- پیداکردن تفاوت Environmentهای توسعه و تولید
- مقایسه Snapshotهای تست
- بررسی تغییر Payloadهای Webhook
- مقایسه Exportهای داده
- تشخیص Regression در قرارداد API
- بازبینی تغییرات قبل از Migration
مثال واقعی: تغییر پاسخ API
// نسخه اول
{
"user": {
"id": 42,
"name": "Ava",
"roles": ["editor"]
},
"meta": {"version": 1}
}// نسخه دوم
{
"user": {
"id": 42,
"fullName": "Ava",
"roles": ["editor","reviewer"]
},
"meta": {"version": "2"}
}یک Diff مناسب باید حداقل این موارد را گزارش کند: حذف user.name، افزودن user.fullName، افزودن عضو جدید به user.roles و تغییر نوع meta.version از Number به String. این گزارش نشان نمیدهد تغییرها از نظر قرارداد API مجاز هستند؛ برای آن باید Schema یا Contract بررسی شود.
خطاهای رایج در تفسیر JSON Diff
یکی دانستن null و نبود کلید
{"value":null} با {} برابر نیست. در اولی کلید وجود دارد؛ در دومی وجود ندارد.
نادیدهگرفتن تغییر نوع
تبدیل true به "true" فقط تغییر ظاهری نیست؛ Boolean به String تبدیل شده است.
فرض اینکه ترتیب کلیدهای Object مهم است
در اغلب کاربردها ترتیب Propertyهای Object نباید مبنای منطق باشد. ابزار متنی ممکن است جابهجایی کلیدها را تغییر بزرگ گزارش کند.
نادیدهگرفتن ترتیب Array
برخلاف Object، ترتیب اعضای Array معمولاً بخشی از داده است. فعالکردن Ignore Order بدون شناخت دامنه میتواند تغییر واقعی را پنهان کند.
مقایسه JSON نامعتبر
اگر یکی از ورودیها Parse نشود، نتیجه ساختاری قابل اعتماد نیست. ابتدا Syntax هر دو ورودی را اصلاح کنید.
Troubleshooting
چرا کل فایل متفاوت نمایش داده میشود؟
احتمالاً ابزار مقایسه متنی انجام میدهد یا فرمت دو فایل متفاوت است. هر دو JSON را Parse و Normalize کنید یا از Structural Diff استفاده کنید.
چرا جابهجایی یک عضو Array چندین تغییر ساخته است؟
ابزار بر اساس Index مقایسه میکند. اگر اعضا شناسه یکتا دارند، حالت Match by Key یا Compare by id مناسبتر است.
چرا عدد و String برابر گزارش شدهاند؟
ممکن است گزینه Loose Comparison فعال باشد. برای بررسی قرارداد داده از Strict Type Comparison استفاده کنید.
چرا تغییر کلید شبیه حذف و افزودن گزارش میشود؟
Rename در JSON عملیات ذاتی جداگانهای ندارد. بیشتر الگوریتمها تغییر نام را حذف کلید قبلی و افزودن کلید جدید میبینند.
نکات عملکرد برای JSONهای بزرگ
- هر دو سند باید Parse شوند و حافظه جداگانه مصرف میکنند.
- مقایسه بازگشتی Objectهای بسیار عمیق میتواند هزینهبر باشد.
- الگوریتم تطبیق Arrayهای بزرگ ممکن است بسیار کندتر از مقایسه Indexمحور باشد.
- نمایش هزاران تفاوت در UI به Virtualization نیاز دارد.
- فیلتر بر اساس Path یا Depth میتواند حجم خروجی را کم کند.
- برای فایلهای بسیار بزرگ، پردازش Streaming فقط در سناریوهای محدود ممکن است.
نکات امنیت و حریم خصوصی
دو فایل مقایسهشده ممکن است شامل Token، اطلاعات شخصی، تنظیمات محرمانه یا داده تولید باشند. قبل از استفاده از ابزار آنلاین، مشخص کنید پردازش در مرورگر انجام میشود یا داده به سرور ارسال میشود. Secretها را Mask کنید، فایلهای حساس را در Log نگه ندارید و برای داده سازمانی از ابزار محلی یا محیط تأییدشده استفاده کنید.
بهترین روشها
- همیشه از Structural Diff برای مقایسه معنایی JSON استفاده کنید.
- Strict Type Comparison را فعال نگه دارید مگر دلیل مشخصی برای مقایسه Loose داشته باشید.
- قانون مقایسه Array را متناسب با داده انتخاب کنید.
- Path هر تغییر را ذخیره کنید تا بررسی و Debug قابل تکرار باشد.
- تفاوتهای بزرگ را بر اساس Added، Removed، Changed و Type Changed دستهبندی کنید.
- نتیجه Diff را جای Schema Validation یا تست تجاری نگذارید.
- برای داده حساس، پردازش محلی را ترجیح دهید.
چکلیست انتخاب ابزار JSON Diff
- آیا هر دو ورودی را جداگانه Parse میکند؟
- آیا تفاوت ساختاری را از تفاوت فرمت جدا میکند؟
- آیا Added، Removed و Changed را تفکیک میکند؟
- آیا تغییر Type را صریح نشان میدهد؟
- آیا Path کامل هر تغییر را ارائه میدهد؟
- آیا برای Array حالتهای مختلف مقایسه دارد؟
- آیا امکان Ignore Path یا Ignore Order دارد؟
- آیا خروجی قابل کپی یا Export است؟
- آیا روی فایل بزرگ عملکرد قابل قبول دارد؟
- آیا سیاست حریم خصوصی روشن است؟
پیادهسازی ساده JSON Diff در JavaScript
برای پروژههای کوچک میتوان یک مقایسه بازگشتی ساده نوشت. این نمونه تفاوت نوع، مقدار، کلیدهای اضافه و حذفشده را ثبت میکند. برای Arrayهای پیچیده، تشخیص Move و تطبیق بر اساس id به منطق بیشتری نیاز دارد.
function diffJson(left, right, path = '$', changes = []) {
if (Object.is(left, right)) return changes;
const leftType = Array.isArray(left) ? 'array' : typeof left;
const rightType = Array.isArray(right) ? 'array' : typeof right;
if (left === null || right === null || leftType !== rightType) {
changes.push({ path, type: 'changed', before: left, after: right });
return changes;
}
if (leftType !== 'object' && leftType !== 'array') {
changes.push({ path, type: 'changed', before: left, after: right });
return changes;
}
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
for (const key of keys) {
const nextPath = Array.isArray(left) ? `${path}[${key}]` : `${path}.${key}`;
if (!(key in left)) changes.push({ path: nextPath, type: 'added', after: right[key] });
else if (!(key in right)) changes.push({ path: nextPath, type: 'removed', before: left[key] });
else diffJson(left[key], right[key], nextPath, changes);
}
return changes;
}این کد برای آموزش مناسب است، اما در محیط تولید باید محدودیت عمق، Circular Reference در ورودیهای غیر JSON، Arrayهای بزرگ، امنیت نمایش خروجی و هزینه حافظه نیز مدیریت شوند. همچنین JSON واقعی Circular Reference ندارد، چون متن JSON قابل Parse نمیتواند به خودش ارجاع دهد.
مقایسه Strict و Loose
در مقایسه Strict، مقدار و نوع داده هر دو اهمیت دارند. بنابراین Number برابر 1 با String برابر "1" یکسان نیست. در مقایسه Loose ممکن است ابزار تلاش کند مقادیر را تبدیل کند و آنها را برابر بداند. این رفتار برای پاکسازی دادههای قدیمی شاید مفید باشد، اما برای API، Schema و تست قرارداد خطرناک است؛ زیرا تغییر نوع را پنهان میکند.
| نمونه | Strict | Loose احتمالی |
|---|---|---|
1 و "1" | متفاوت | ممکن است برابر |
true و "true" | متفاوت | بسته به ابزار |
null و نبود کلید | متفاوت | نباید برابر شود |
0 و false | متفاوت | ممکن است تبدیل شود |
نادیدهگرفتن مسیرهای پویا
بعضی پاسخها فیلدهایی مانند زمان تولید، شناسه درخواست یا ترتیب موقت دارند که در هر اجرا تغییر میکنند. در این شرایط میتوان Pathهای مشخصی را از مقایسه حذف کرد؛ برای نمونه $.meta.generatedAt یا $.requestId. این قابلیت باید محدود و مستند باشد. نادیدهگرفتن الگوهای گسترده ممکن است Regression واقعی را مخفی کند.
بهترین روش این است که فهرست Ignoreها در کنار تست نگهداری شود، دلیل هر مورد نوشته شود و پس از تغییر قرارداد دوباره بازبینی گردد. حذف تصادفی همه فیلدهای زمان یا شناسه، نتیجه مقایسه را بیش از حد خوشبینانه میکند.
استفاده از JSON Diff در تست خودکار
در Snapshot Testing یا Contract Testing، خروجی واقعی با خروجی مورد انتظار مقایسه میشود. گزارش مناسب باید فقط نخستین خطا را نشان ندهد، بلکه مجموعهای محدود و خوانا از تفاوتها را ارائه کند. برای جلوگیری از خروجی بسیار بزرگ میتوان حداکثر تعداد تغییر، عمق گزارش و طول Preview مقدارها را محدود کرد.
- قبل از مقایسه، دادههای تصادفی را با قواعد مشخص Normalize کنید.
- نوع دادهها را Strict مقایسه کنید.
- برای Array موجودیتها، کلید تطبیق ثابت تعیین کنید.
- Snapshot را بدون بازبینی انسانی بهروزرسانی نکنید.
- تغییرهای Breaking را از تغییرهای افزایشی جدا گزارش کنید.
چگونه تفاوتهای Breaking را تشخیص دهیم؟
خود Diff فقط تغییر را نشان میدهد، اما Breaking بودن به مصرفکننده داده بستگی دارد. حذف کلید موردنیاز، تغییر Number به String، تبدیل Object به Array یا حذف عضو Enum معمولاً میتواند ناسازگار باشد. افزودن یک کلید اختیاری اغلب سازگار است، مگر اینکه مصرفکننده Unknown Property را رد کند. بنابراین برای تشخیص قطعی باید نتیجه Diff با JSON Schema، نسخه API و رفتار Client ترکیب شود.
جمعبندی
مقایسه دو JSON فقط پیدا کردن تفاوت خطوط نیست. JSON Diff باید داده را بهصورت ساختاری بررسی کند و افزودن، حذف، تغییر مقدار، تغییر نوع و تفاوت Arrayها را با مسیر دقیق گزارش دهد. مهمترین تصمیم در این فرایند، انتخاب روش مقایسه Array و تفکیک تغییر معنایی از تغییر ظاهری است. نتیجه Diff برای Debug و بازبینی بسیار مفید است، اما اعتبار قرارداد، امنیت و منطق تجاری باید با Schema، تست و بررسی مستقل تأیید شوند.