مقارنة JSON

قارن مستندَي JSON وشاهد ما أُضيف وما حُذف وما تغيّر. ترتيب المفاتيح والمسافات البادئة والتصغير كلها متجاهَلة، ولا شيء يغادر متصفحك.

JSON الأول
JSON الثاني
النتيجة

ألصق JSON في اللوحتين لمقارنتهما

مقارنة البيانات لا النص

المقارنة سطرًا بسطر تجيب عن سؤال يخص الكتابة: أي سطور هذا الملف ليست سطورًا في ذاك. وهو السؤال الصحيح للنثر وللشيفرة المصدرية، والخاطئ لـ JSON، لأن مستند JSON ليست له طريقة كتابة صحيحة واحدة. أعد ترتيب أعضاء الكائن، أو غيّر المسافات البادئة، أو اضغط أحد الجانبين، فيتغير كل سطر بينما لا تتزحزح البيانات قيد أنملة. تقارن هذه الأداة ما يؤول إليه المستندان بعد التحليل، فما تبلغ عنه هو ما تغير فعلًا. في الأسفل مستند واحد مكتوب مرتين: مضغوطًا في السطر الأول، ثم منسقًا وأعضاؤه في ترتيب آخر.

{"name":"checkout","port":8080,"tags":["a","b"]}

{
  "port": 8080,
  "tags": ["a", "b"],
  "name": "checkout"
}

هذان المستندان لا يشتركان في سطر واحد، ومع ذلك لا تبلغ هذه الأداة عن أي فرق بينهما: الأعضاء نفسها، والقيم نفسها، والترتيب نفسه داخل القائمة. والنتيجة العملية أنك لست مضطرًا أبدًا إلى إعادة تنسيق مستند كي تنجح المقارنة، إذ يعني ذلك تغيير الشيء الذي جئت لمقارنته أصلًا.

ما لا يُحتسب فرقًا أبدًا

أربعة أمور تخص طريقة كتابة المستند يجري تجاهلها، وتُتجاهل دون شرط. ولا يوجد أي مِقبض يعيد أيًا منها إلى العمل، لأن أداة تقدر على ذلك ستكون أداتين بمعنيين يحتاجان إلى شرح، وسيلزم القارئ أن يعرف أيهما أنتج الجواب الماثل أمامه.

  • ترتيب كتابة أعضاء الكائن. {"a": 1, "b": 2} و{"b": 2, "a": 1} يحملان البيانات نفسها، فالمُسلسِل الذي يخرج مفاتيحه بترتيب مختلف في كل تشغيل لا يظهر هنا تغييرًا أبدًا.
  • المسافات البيضاء بكل أنواعها — المسافات البادئة، وفواصل الأسطر، والمسافة بعد النقطتين. المستند المضغوط والمستند المنسق إذا حملا البيانات نفسها فهما المستند نفسه مرتين.
  • طريقة كتابة العدد. 1e3 هو 1000 و1.50 هو 1.5، لأن العدد في JSON له قيمة وليس له هجاء: مستندان لا يختلفان إلا في ذلك يحملان الأعداد نفسها، ولا يُبلَّغ عنها بشيء البتة.
  • صيغة هروب محرف داخل سلسلة نصية. المحارف الستة ‎\u0041 والحرف A هما السلسلة نفسها ذات المحرف الواحد.

وأمران يبدو أن مكانهما تلك القائمة ليسا فيها عن قصد. فترتيب عناصر القائمة بيانات — ‎[1, 2] و‎[2, 1] مستندان مختلفان — وعضو قيمته null ليس كعضو غائب أصلًا، فالواحد في مقابل الآخر يُبلَّغ عنه بدل أن يمر في صمت.

مضاف ومحذوف ومتغيّر — وتغيّر في النوع

كل صف من النتيجة هو بالضبط من واحد من ثلاثة أصناف، وهي معًا تغطي كل ما تستطيع المقارنة أن تجده. خذ هذين المستندين:

{"host":"api.example.com","port":8080,"debug":true}

{"host":"api.example.com","port":"8080","retries":3}
  • متغيّر عند ‎$['port']: العضو موجود في المستندين وقيمته ليست واحدة، فتظهر القيمتان معًا. وهذا الصف موسوم كذلك بتغيّر في النوع، لأن 8080 عدد و"8080" سلسلة نصية.
  • محذوف عند ‎$['debug']: العضو موجود في المستند الأول وغائب عن الثاني.
  • مضاف عند ‎$['retries']: العضو موجود في المستند الثاني وغائب عن الأول.

أما العضو المسمى host فموجود في الاثنين بالقيمة نفسها، فلا يُبلَّغ عنه إطلاقًا. وتغيّر النوع خاصية من خواص التغيير لا صنف رابع من الصفوف: هو الانحراف الذي ينتبه له المخطط ولا ينتبه له غالبًا من يقرأ ملفين، فيُذكر حيث يقع بدل أن يُترك لعينك.

موضع الفرق مكتوبًا مسارًا

كل صف يسمي الموضع الذي يجلس فيه، ويسميه مسارًا داخل المستند لا رقم سطر — فرقم السطر حقيقة عن الكتابة، وهي بعينها ما فرغت هذه الأداة لتوها من تجاهله. والمسارات مسارات مُطبَّعة وفق RFC 9535، وهي الكتابة الوحيدة التي يستعملها هذا الموقع حيثما وجب تدوين موضع داخل JSON.

  • $ هو المستند كله، و‎$['port'] هو العضو المسمى port فيه، و‎$['hosts'][1] هو العنصر عند الفهرس 1 من القائمة تحت hosts. ويُكتب العضو دائمًا بين قوسين معقوفين وعلامتي اقتباس، فالاسم الذي يحوي نقطة أو مسافة أو قوسًا خاصًا به لا يحتاج إلى شيء استثنائي.
  • المسار استعلام يمكنك تشغيله. ألصقه في مُختبِر JSONPath في هذا الموقع مع المستند الذي ينتمي إليه فيختار تلك العقدة بالضبط، وهي أسرع طريقة لقراءة فرق ضمن محيطه.
  • وهو استعلام على المستند الذي توجد فيه العقدة فعلًا: مسار العضو المضاف يعمل على المستند الثاني، ومسار العضو المحذوف على الأول. ولا شيء هنا يسمي عقدة غير موجودة.

وقد يحمل الصف المتغيّر مسارين، وذلك هو التطابق الوارد في القسم التالي وقد بان من خلاله: ما إن تكسب القائمة عنصرًا أو تفقده حتى يجلس العنصر نفسه عند فهرس مختلف في كل جانب. وحيث يتطابق المساران — وهو الغالب دائمًا — يظهر أحدهما فقط.

كيف تُطابَق قائمتان

يُقارن الكائنان عضوًا بعضو، وذلك يسير لأن للعضو اسمًا. أما القائمتان فلا أسماء لهما بل مواضع فحسب، ومقارنتهما موضعًا بموضع هي ما يجعل أكثر مخرجات المقارنة غير قابلة للقراءة: أدخل سجلًا واحدًا في مقدمة قائمة طويلة فلا يبقى شيء في موضعه، فتبلغ المقارنة التي تقرأ الموضع بوصفه هوية عن القائمة كلها بوصفها مختلفة — صحيح بالمعنى الضيق وعديم النفع بكل معنى آخر. لذلك تُطابَق العناصر أولًا، بحسب ما هي عليه لا بحسب حقل تسميه أنت، ولا يُبلَّغ إلا عما يفيض عن التطابق.

{"hosts": ["alpha", "beta", "gamma"]}

{"hosts": ["alpha", "staging", "beta", "gamma"]}

هذا صف واحد: أُضيف staging عند ‎$['hosts'][1]. أما العنصران المسميان beta وgamma فقد انزاحا موضعًا واحدًا ولا يُذكران، لأن الانزياح ليس تغييرًا. والتطابق نفسه هو ما يتيح أن يُبلَّغ عن حقل واحد حُرِّر داخل عنصر من القائمة بوصفه تغييرًا في ذلك الحقل، لا بوصف العنصر كله قد استُبدل بآخر.

والترتيب لا يزال محسوبًا، والتطابق لا يدّعي غير ذلك: ‎[1, 2] في مقابل ‎[2, 1] يعود بحذف 1 من المقدمة وإضافة 1 بعد 2، وهذا ما جرى فعلًا. وقد تكون قائمتان مختلفتين أكثر من أن يستحق التطابق كلفته، وبعد تلك النقطة ترتد المقارنة إلى المضي موضعًا بموضع وتقول ذلك في رأس النتيجة — فيغدو الجواب الصاخب مشروحًا بدل أن يكون لغزًا.

التقرير والشجرة

تُقدَّم المقارنة نفسها في عرضين، والتنقل بينهما لا يعيد حساب شيء: فالشجرة تُرسم من قائمة الفروق نفسها التي يعددها التقرير، فلا يمكن أن يختلف الاثنان على ما تغير ولا على مقداره.

  • التقرير يجيب عن سؤال ما الذي تغير: صف لكل فرق، يحمل كل منها مساره وصنفه وقيمته في كل جانب. وهو العرض الذي تمسحه بعينك حين تريد القائمة كلها أمامك.
  • والشجرة تجيب عن سؤال أين في المستند: المستندان مصهوران في مخطط واحد، ولا يُفتح فيه إلا ما يحمل فرقًا. وكل امتداد من الأبناء المتتالين لا يحمل شيئًا يُطوى إلى سطر واحد يقول عن كم ينوب — فالزوج الوارد في القسم أعلاه يُرسم في خمسة صفوف: المستند، والقائمة داخله، وسطر مطوي ينوب عن عنصر واحد، والعنصر المضاف، وسطر مطوي ينوب عن اثنين.
  • والعدد بجانب عنوان النتيجة هو كل الفروق الموجودة. وحين تُقتطع القائمة على الشاشة بسبب الطول لا يتزحزح ذلك العدد، ويعرض مِقبض في ذيل القائمة بقيتها.
~! $['port'] 8080 -> "8080"
- $['debug'] true
+ $['retries'] 3

هذا ما ينتجه مِقبض النسخ للمستندين الواردين في القسم الثالث: علامة للصنف، ثم المسار، ثم القيمتان بينهما سهم — زائد للعضو المضاف، وناقص للمحذوف، وعلامة تلدة للتغيير، وتلدة تتبعها علامة تعجب حيث تغير النوع أيضًا. وقد كُتب بالرموز لا بالكلمات عن قصد، كي تكون النتيجة الملصوقة النص نفسه بأي لغة من لغات الموقع كنت تقرأ، وهو لا يقصّر شيئًا: القيمة التي اقتطعتها الشاشة موجودة فيه كاملة.

ما يكلفه التحليل، وما تقوله الأداة جهرًا

أن تقارن ما يؤول إليه مستندان بعد التحليل يعني أن تتعايش مع كلفة ذلك التحليل، وتحليل JSON ليس بلا فقد. وهذا هنا أثقل منه في أي موضع آخر في هذا الموقع، لأن الجواب الذي لا تملك أداة مقارنة أن تحتمله هو «لا فرق» بين مستندين يختلفان فعلًا. لذلك يُقرأ نص كل لوحة مرة ثانية بوصفه نصًا، وما أسقطه التحليل يُبلَّغ عنه بجوار المقارنة بدل أن يُبتلع.

  • اسم عضو مكتوب مرتين داخل الكائن نفسه. لا ينجو من التحليل إلا النسخة الأخيرة، فلم تُقارَن إلا هي، وأما الأولى فلم يرها أحد.
  • عدد أطول من أن ينجو بدقة مزدوجة. قد يتحول حرفيّان مختلفان بالتحليل إلى القيمة نفسها، والمعرفات هي بالضبط الموضع الذي يقع فيه ذلك: 12345678901234567890 و12345678901234567891 عددان صحيحان مختلفان وعدد واحد بعد التحليل، فالمقارنة وحدها كانت ستسمي مستندين تختلف معرفاتهما مستندين متطابقين.
{"user": {"id": 1}, "user": {"id": 2}}

يتحول ذلك المستند بالتحليل إلى {"user": {"id": 2}}، وتقول الأداة ذلك تحت اللوحة التي أُلصق فيها، مع سطر الحالة الأولى وعمودها وكم منها إجمالًا. ولا يوقف أي من الفقدين المقارنة؛ بل يقفان إلى جوارها. والمستند الذي لا يتحلل أصلًا يُبلَّغ عنه في الموضع نفسه، مع موضع المشكلة حيثما وُجد موضع يُذكر، وتُقرأ اللوحتان مستقلتين — فمستندان معطوبان ينتجان خطأين معًا لا واحدًا بعد الآخر. أما المستند المتداخل أعمق بكثير مما يمكن رسم قيمة عنده فيُرفض من فوره، وذلك رفض لا لسان تبويب معلقة.

لماذا لا يغادر شيء مما تلصقه لسان التبويب هذا

يبقى المستندان كلاهما في متصفحك. لا رفع ولا طلب ولا خادوم يمكن أن يحتفظ بنسخة: المقارنة حساب يجري داخل الصفحة التي حملتها بالفعل، وهذا أيضًا سبب استمرارها في العمل والشبكة مطفأة. وهذا ما يجعل لصق استجابة من بيئة الإنتاج أو سجل عميل أو حمولة موقعة هنا آمنًا.

ولا يُحفظ شيء كذلك. أعد تحميل الصفحة فتجد اللوحتين فارغتين؛ لا سجل ولا حساب ولا شيء تحذفه بعدها. والشيء الوحيد الذي يخرج يومًا هو ما تنسخه أنت بنفسك.

الأسئلة الشائعة

هل تتجاهل الأداة ترتيب المفاتيح؟
نعم، دائمًا، وذلك هو سبب وجود الأداة. فالكائنان اللذان يحملان الأعضاء نفسها بالقيم نفسها متطابقان مهما كانت طريقة كتابتهما، فالمُسلسِل الذي يخرج مفاتيحه بترتيب مختلف في كل تشغيل لا يظهر تغييرًا أبدًا. والمسافات البادئة والضغط وطريقة كتابة العدد وصيغة هروب المحرف تُتجاهل كلها للسبب نفسه، وليس أي منها إعدادًا كان بوسعك قلبه.
هل العضو الذي قيمته null هو نفسه العضو الغائب؟
لا، وليس ثمة خيار لمعاملتهما سواء. {"note": null} في مقابل {} يُبلَّغ عنه بوصفه ‎$['note'] محذوفًا. فالحقل الحاضر الفارغ قول آخر غير الحقل الذي لم يرسله أحد — وكل لغة مخططات وكل لغة ذات أنواع ساكنة تفرق بينهما — فدمجهما هنا يرمي شيئًا تقوله المستندات فعلًا.
هل يُبلَّغ عن ‎[1, 2] و‎[2, 1] بوصفهما متماثلين؟
لا. مصفوفة JSON مرتبة، فالقائمتان اللتان تحملان العناصر نفسها بترتيب مختلف مستندان مختلفان، وتسميتهما متساويتين تجعل هذه الأداة تقول عن الصيغة قولًا غير صحيح. والذي تحصل عليه حذف واحد وإضافة واحدة: حُذف 1 من المقدمة وأُضيف ثانية بعد 2. والكائنات على العكس، لأن أعضاءها مسماة لا موضوعة في مواضع.
ماذا يعني المسار في كل صف؟
هو موضع الفرق داخل المستند مكتوبًا مسارًا مُطبَّعًا وفق RFC 9535: فـ$ هو المستند نفسه، و‎$['port'] عضو فيه، و‎$['hosts'][1] العنصر عند الفهرس 1 من القائمة تحت hosts. وهو أيضًا استعلام يمكنك تشغيله — ألصقه في مُختبِر JSONPath في هذا الموقع مع المستند الذي ينتمي إليه فيختار تلك العقدة بالضبط، ولا شيء سواها.
لماذا أظهر أحد الصفوف مسارين مختلفين؟
لأن تطابق قائمتين قد يترك العنصر نفسه عند فهرس مختلف في كل جانب. قارن ["alpha", "beta"] بـ["intro", "alpha", "beta!"] تحصل على صفين: أُضيف intro عند ‎$[0]، وتغير beta إلى beta! — وهو ‎$[1] في المستند الأول و‎$[2] في الثاني. وحيث يتطابق المساران، وهو الغالب، يظهر أحدهما فقط.
ماذا يعني تغيّر النوع في صف ما؟
يعني أن القيمة ليست مختلفة فحسب بل صارت شيئًا من نوع آخر: عدد صار سلسلة نصية، أو قيمة صارت null، أو كائن صار قائمة. والمنفذ المكتوب 8080 في جانب و"8080" في الآخر هو الحالة الأشيع، وغالبًا ما يعني أن مُسلسِلًا أو عميلًا هو الذي تغير لا البيانات. وهو موسوم على الصف لأن القيمة المحررة والعقد المنحرف يطلبان منك ردين مختلفين.
مستنداي يبدوان متطابقين لكن أحدهما يكتب المفتاح نفسه مرتين. ماذا يحدث؟
يحتفظ التحليل بالنسخة الأخيرة ويسقط الأولى، فترى المقارنة عضوًا واحدًا وقد لا تبلغ عن أي فرق. وتقرأ الأداة النص الخام على حدة لهذا السبب بالذات، فتقول تحت تلك اللوحة إن اسمًا مكتوب مرتين، وكم مرة إجمالًا، وأين الأولى. وتلتقط القراءة الثانية نفسها عددًا صحيحًا أطول من أن ينجو بدقة مزدوجة، وهي الطريقة الأخرى التي يمكن بها لمستندين مختلفين أن يتحللا إلى مستندين غير مختلفين.
هل يمكنني الحصول على رقعة أطبقها؟
لا، وذلك قرار لا نقص. فرقعة JSON تُطبق عملية إثر عملية، فتتزحزح الفهارس داخلها أثناء تشغيلها؛ وإصدار واحدة وعد بخصوص برنامج ستشغله على بياناتك أنت، وهو ادعاء أكبر بكثير من وصف ما يختلف. والذي يمكنك أخذه بدلًا من ذلك هو التقرير نصًا، من مِقبض النسخ — وهو نفسه بكل لغات الموقع، وغير مقصَّر في أي موضع.
هل يُرسل شيء مما ألصقه إلى خادوم؟
لا. يُقارن المستندان في متصفحك ولا يغادره أي منهما: لا رفع ولا طلب ولا شيء يُحفظ بين الزيارات. وهذا ما يجعل لصق استجابة من بيئة الإنتاج هنا آمنًا، وهو سبب استمرار الأداة في العمل والشبكة مطفأة. أعد تحميل الصفحة فتعود اللوحتان فارغتين.

أدوات ذات صلة

  • مقارنة النصوص

    حين يكون التغيير الذي تريد رؤيته في الكتابة لا في البيانات — إعادة ضبط المسافات البادئة، أو مفتاح انتقل من موضعه، أو ملفان ليسا JSON أصلًا — فالمقارنة التي تُظهره هي المقارنة سطرًا بسطر. هذا بالضبط ما تفعله تلك الصفحة، مع خيارات للمسافات وحالة الأحرف ورقعة (patch) قابلة للنسخ.

  • منسّق JSON

    يتحقق من JSON ويجمّله، مع تحديد أماكن الأخطاء بوضوح.

  • منسّق SQL

    ينسّق SQL ويجمّله — بعدة لهجات.

  • منسّق XML

    ينسّق XML ويتحقّق منه — تجميل أو تصغير.