So Sánh JSON
So sánh hai đối tượng JSON và hình dung các sự khác biệt được làm nổi bật.
Về so sánh JSON
So sánh JSON (cũng gọi là diff JSON) xác định các sự khác biệt giữa hai cấu trúc JSON. Khác với một diff văn bản thuần, một so sánh hiểu JSON biết các khóa và các giá trị, vì vậy có thể chỉ cho bạn chính xác các thuộc tính nào đã được thêm, xóa hoặc sửa đổi · không phụ thuộc vào thứ tự của các khóa.
Công cụ này thực hiện một so sánh sâu và đệ quy của hai đối tượng JSON. Nó xử lý các đối tượng lồng nhau, các mảng, các chuỗi, các số, các boolean và các giá trị null. Tất cả xử lý diễn ra cục bộ trong trình duyệt của bạn.
Một lịch sử ngắn về JSON
Tiểu sử của JSON ngắn hơn ngôn ngữ lập trình trung bình và ít kịch tính hơn nhiều. Từ viết tắt bắt nguồn từ State Software, Inc., một công ty nhỏ mà Douglas Crockford và Chip Morningstar thành lập vào tháng 3 năm 2001 để xây dựng những gì sau này được gọi là ứng dụng web Ajax. Tin nhắn JSON đầu tiên được gửi vào tháng 4 năm 2001, từ một máy tính trong nhà để xe ở Vùng Vịnh của Morningstar. Crockford không tuyên bố đã phát minh ra JSON, ông đã tách nó từ cú pháp literal đối tượng JavaScript, đặt tên cho nó, và lập một trang web. Ông đã mua json.org vào năm 2002 và đăng một ngữ pháp sơ đồ đường sắt ở đó. Trong bốn năm, JSON lan truyền bằng truyền miệng và một dòng chảy chậm của các triển khai thư viện. Vào tháng 12 năm 2005, Yahoo! bắt đầu cung cấp một số dịch vụ web của mình bằng JSON, khoảnh khắc mà hầu hết các nhà sử học chỉ ra là JSON băng qua dòng chính. Tiêu chuẩn hóa đến trong các đợt sóng: RFC 4627 (tháng 7 năm 2006), do chính Crockford viết, với trạng thái thông tin chứ không phải standards-track; ấn bản đầu tiên ECMA-404 (tháng 10 năm 2013); RFC 7159 (tháng 3 năm 2014), đặt JSON vào IETF Standards Track và nới lỏng yêu cầu rằng giá trị cấp cao nhất phải là đối tượng hoặc mảng. Các phiên bản hiện tại là RFC 8259 (tháng 12 năm 2017), hiện là Internet Standard STD 90, và ấn bản thứ hai ECMA-404 (tháng 12 năm 2017). Cả hai đều giống nhau về ngữ pháp một cách cố ý; văn bản IETF thêm hướng dẫn bảo mật và khả năng tương tác mà văn bản Ecma cố ý bỏ qua.
Hai họ thuật toán: Line Diff và Structural Diff
Khoa học máy tính đã suy nghĩ về diffing từ những năm 1970. Douglas McIlroy (cùng McIlroy đã phát minh ra pipe Unix) và James W. Hunt đã viết Unix diff nguyên bản vào đầu những năm 1970; nó được giao như một phần của Phiên bản thứ 5 của Unix vào năm 1974, với thuật toán cơ bản được ghi lại trong Bell Labs Computing Science Technical Report #41 vào tháng 6 năm 1976. Hunt-McIlroy được xây dựng trên bài toán subsequence chung dài nhất, tìm chuỗi dòng dài nhất xuất hiện theo thứ tự trong cả hai đầu vào, và mọi thứ không có trong chuỗi đó hoặc là xóa hoặc là chèn. Một thập kỷ sau, Eugene W. Myers đã xuất bản "An O(ND) Difference Algorithm and Its Variations" trong Algorithmica Tập 1, Số 2 (1986), định khung lại bài toán như tìm đường đi ngắn nhất trên một "edit graph", cơ sở cho một triển khai Unix diff nhanh hơn chạy nhanh gấp hai đến bốn lần so với người tiền nhiệm. Hôm nay, git diff mặc định là thuật toán histogram trong LibXDiff, thường nhanh hơn Myers vanilla và tạo ra đầu ra dễ đọc hơn, đặc biệt xung quanh các khối được di chuyển. Patience diff (Bram Cohen, 2008) là một tinh chỉnh LCS khác ưu tiên các dòng neo duy nhất. Tất cả các biến thể trên một chủ đề: LCS từng dòng, xử lý đầu vào như một chuỗi phẳng các token mờ. Không ai trong số họ biết JSON là gì.
Họ structural diff có cách tiếp cận khác: đi qua cả hai đầu vào song song, so sánh các nguyên hàm với bình đẳng nghiêm ngặt, tính toán union của các khóa đối tượng (chỉ-có-ở-trái là "đã xóa", chỉ-có-ở-phải là "đã thêm", có-ở-cả-hai có nghĩa là đệ quy), và đi qua các mảng theo chỉ số. Đối với mỗi vị trí, đệ quy. Nếu một mảng dài hơn, các phần tử thừa được thêm vào hoặc bị xóa. Thay đổi kiểu (số vs chuỗi, đối tượng vs mảng) trở thành các mục "type changed". Đây là thuật toán mà jsondiffpatch triển khai như trường hợp cơ sở của nó, và đây là thuật toán mà công cụ này triển khai. Sức mạnh của nó là đầu ra có ý nghĩa: mỗi thay đổi được báo cáo có một đường dẫn bên trong tài liệu (user.address.city, items[3].price) và ngữ nghĩa rõ ràng. Điểm yếu của nó là bước thứ ba, so sánh mảng theo chỉ số, tạo ra vô nghĩa bất cứ khi nào một phần tử được chèn gần phía trước của một mảng dài.
Bài toán khó nhất trong JSON Diffing: Mảng
Giả sử JSON "before" của bạn chứa mảng ["alpha", "beta", "gamma", "delta"] và JSON "after" của bạn chứa ["alpha", "new", "beta", "gamma", "delta"]. Một diff dựa trên chỉ số ngây thơ báo cáo bốn thay đổi: chỉ số 1 đã thay đổi từ "beta" sang "new"; chỉ số 2 đã thay đổi từ "gamma" sang "beta"; chỉ số 3 đã thay đổi từ "delta" sang "gamma"; chỉ số 4 đã thêm "delta". Một con người nhìn vào điều này sẽ nói rằng có một thay đổi: một lần chèn duy nhất ở chỉ số 1. Đây là cùng một bài toán LCS mà họ line-diff đã giải vào những năm 1970, áp dụng cho các mảng giá trị JSON thay vì các mảng dòng văn bản. jsondiffpatch làm chính xác điều đó, tính LCS của hai mảng và phát ra các phần chèn và xóa liên quan đến nó. Đối với mảng các nguyên hàm, điều này hoạt động tốt. Đối với mảng các đối tượng thì không: [{id: 1, name: "Alice"}] so với [{id: 1, name: "Alicia"}] không có subsequence chung nào bằng cách so sánh tham chiếu, vì vậy LCS ngây thơ báo cáo "xóa toàn bộ phần tử trái, thêm toàn bộ phần tử phải" trong khi sự thật là một field của một phần tử đã thay đổi. Giải pháp của jsondiffpatch là callback objectHash: người gọi cung cấp một hàm ánh xạ mỗi đối tượng đến một khóa nhận dạng (thường là obj => obj.id), và diff khớp các phần tử mảng theo nhận dạng thay vì theo tham chiếu. Đây là một vấn đề chung khó khăn mà không ai đã giải quyết hoàn toàn. Công cụ Absolutool, theo FAQ của chính nó, so sánh các mảng theo chỉ số, không thực hiện LCS hoặc objectHash. Đó là một lựa chọn phạm vi có chủ ý cho một công cụ nhỏ miễn phí: nó xuất sắc trong các diff đối tượng và yếu hơn trên các mảng nơi các phần tử đã được chèn hoặc sắp xếp lại.
JSON Patch (RFC 6902): Diff như một định dạng vận chuyển
RFC 6902, "JavaScript Object Notation (JSON) Patch," được xuất bản vào tháng 4 năm 2013. Nó định nghĩa loại media application/json-patch+json và một ngôn ngữ patch sáu hoạt động: add, remove, replace, move, copy, test. Một patch chính nó là một tài liệu JSON, một mảng các đối tượng hoạt động, mỗi đối tượng có một khóa op, một khóa path (một JSON Pointer đến vị trí mục tiêu), và một field value hoặc from tùy theo nhu cầu. Các hoạt động được áp dụng theo thứ tự, một cách atomic: nếu bất kỳ một hoạt động nào thất bại, toàn bộ patch sẽ bị từ chối. Cú pháp đường dẫn đến từ RFC 6901, "JSON Pointer," một tài liệu đồng hành được xuất bản cùng tháng, một cú pháp chuỗi nhỏ của các mã thông báo tham chiếu được phân tách bằng dấu gạch chéo (/user/address/city có nghĩa là "giá trị tại khóa city bên trong address bên trong user"; /items/3 có nghĩa là "phần tử thứ tư của items", được lập chỉ mục từ không). Vì / và ~ là đặc biệt, chúng được escape thành ~1 và ~0 tương ứng (theo thứ tự đó, giải mã ~1 trước để tránh lỗi giải mã đôi). JSON Patch là định dạng đầu ra phù hợp cho công cụ diff JSON muốn cấp kết quả cho máy khác. Phương thức HTTP PATCH (RFC 5789) được thiết kế chính xác cho loại payload cập nhật một phần này. Kubernetes hỗ trợ RFC 6902 cùng với định dạng strategic-merge-patch của riêng nó. Công cụ này hiện không phát ra JSON Patch, đó là hướng tính năng tương lai, không phải hiện tại.
JSON Merge Patch (RFC 7396): Lựa chọn thay thế đơn giản hơn, lossy
RFC 7396, "JSON Merge Patch," được xuất bản vào tháng 10 năm 2014 bởi Paul Hoffman và James Snell. Nó không sử dụng hoạt động nào cả, tài liệu patch chỉ đơn giản là một đối tượng JSON một phần phủ lên bản gốc. Bất kỳ field nào hiện diện trong patch sẽ ghi đè field tương ứng trong bản gốc; bất kỳ field nào được đặt thành null trong patch sẽ loại bỏ field đó; bất kỳ field nào vắng mặt từ patch sẽ không thay đổi; mảng được thay thế hoàn toàn, không bao giờ được hợp nhất. Merge Patch ngắn gọn và dễ viết bằng tay. Sự đánh đổi là mất khả năng biểu đạt: không có cách nào để đặt giá trị của một field thành null theo nghĩa đen (vì null có nghĩa là "xóa"); không có cách nào để sửa đổi một phần tử duy nhất của một mảng (toàn bộ mảng được thay thế); không có cách nào để diễn đạt sự di chuyển hoặc sao chép; không có cách nào để phân biệt "tôi không chạm vào field này" với "tôi muốn đặt nó thành giá trị hiện tại của nó". Đối với hầu hết các cập nhật API hàng ngày, những hạn chế này có thể chấp nhận được, đó là lý do tại sao Merge Patch là phổ biến hơn trong hai định dạng. RFC 6902 thắng trong các kịch bản mà bất kỳ hạn chế nào cắn. Kubernetes sử dụng cả hai định dạng một cách có chủ ý, chọn cái phù hợp cho từng bối cảnh.
Cách Diff trực quan được render
Có ba quy ước trực quan phổ biến để hiển thị một diff cấu trúc. Cạnh nhau, hai cột, bản gốc bên trái, đã sửa đổi bên phải, với các dòng tương ứng được căn chỉnh (layout mà công cụ này sử dụng cho các đầu vào của nó). Diff thống nhất nội tuyến, một cột duy nhất hiển thị các dòng đã xóa (thường có tiền tố - và được tô màu đỏ), các dòng được thêm vào (+, xanh lá), và các dòng ngữ cảnh không thay đổi (không có tiền tố, văn bản thuần), layout mà git diff sử dụng theo mặc định. Chế độ xem cây, render JSON dưới dạng một cây có thể thu gọn, với các nút đã thay đổi được làm nổi bật và các nút không thay đổi được gấp lại. Quy ước màu sắc nhất quán đáng chú ý trên toàn hệ sinh thái: xanh lá cho bổ sung, đỏ cho xóa bỏ, vàng hoặc hổ phách cho giá trị đã thay đổi, xám trung tính cho không thay đổi. Công cụ này tuân theo quy ước chính xác: xanh lá nhạt cho bổ sung, đỏ nhạt cho xóa bỏ, vàng nhạt cho thay đổi. Hầu hết người dùng sẽ nhận ra màu sắc từ GitHub, GitLab, BitBucket, mọi chế độ xem diff IDE và hàng tá công cụ trực tuyến.
Các sử dụng phổ biến
- So sánh các phản hồi API trước và sau một sửa đổi mã
- Tìm các sự khác biệt giữa các tệp cấu hình
- Xem xét các lockfile được tạo.
package-lock.json,yarn.lock,poetry.lock,Cargo.lockvàterraform.tfstateđều là các tệp JSON-hoặc-JSON-liền kề được tạo bởi máy thay đổi theo những cách ồn ào trong quá trình phát triển bình thường. Một diff nhận biết JSON hữu ích hơn nhiều so với một diff văn bản để hiểu những gì thực sự đã di chuyển. - Xác minh các chuyến đi khứ hồi tuần tự hóa. Tuần tự hóa một hàng cơ sở dữ liệu thành JSON, chuyển nó qua một mạng, giải tuần tự hóa nó, tuần tự hóa lại. Có gì thay đổi không? Một diff bắt các lỗi im lặng trong các bộ tuần tự hóa tùy chỉnh, đặc biệt là xung quanh ngày tháng, độ chính xác số và xử lý null.
- Kiểm tra hồi quy đầu ra JSON. Snapshot một đầu ra JSON được biết là tốt; diff mỗi lần chạy tiếp theo so với snapshot và làm thử nghiệm thất bại nếu có gì thay đổi.
- So sánh các bản nháp lược đồ. Các tài liệu JSON Schema và OpenAPI tự chúng là JSON. Diff hai phiên bản của một lược đồ tiết lộ chính xác field nào trở nên bắt buộc, tùy chọn hoặc có các kiểu được thắt chặt.
- Xác minh tính chính xác của một di chuyển dữ liệu
Các trường hợp biên đáng biết
Độ chính xác số. RFC 8259 nói rằng các số JSON là độ chính xác tùy ý và không đặt giới hạn trên về số chữ số mà một số có thể có. Tuy nhiên, JavaScript phân tích cú pháp mọi số dưới dạng IEEE 754 double 64-bit. Trên Number.MAX_SAFE_INTEGER (253 − 1 = 9.007.199.254.740.991), các số nguyên có thể âm thầm mất độ chính xác khi được chuyến đi khứ hồi qua một diff dựa trên JavaScript. Cùng một vấn đề ảnh hưởng đến dấu chấm động, 0.1 + 0.2 nổi tiếng không chính xác 0.3 trong IEEE 754. JSON nghiêm ngặt cũng không cho phép dấu phẩy trailing và bình luận; cả hai đều là lỗi cú pháp theo RFC 8259. JSON5 (json5.org) là một superset chính thức cho phép bình luận, dấu phẩy trailing, dấu nháy đơn, khóa không có dấu nháy, số hex, dấu thập phân đầu/cuối, và Infinity/NaN. JSONC là chế độ "JSON với Bình luận" của Microsoft được sử dụng trong các tệp cài đặt VS Code. Công cụ này sử dụng JSON.parse() và sẽ từ chối cả hai, hãy loại bỏ bình luận và dấu phẩy trailing trước khi dán.
Chuỗi ngày tháng. JSON không có kiểu ngày tháng native. Ngày tháng thường được mã hóa dưới dạng chuỗi, với tiêu chuẩn de-facto là ISO 8601 (và đặc biệt là hồ sơ RFC 3339 mà hầu hết các API internet sử dụng): YYYY-MM-DDTHH:MM:SS[.fff]Z. Một diff so sánh ngày tháng dưới dạng chuỗi sẽ báo cáo 2024-01-01T00:00:00Z và 2024-01-01T00:00:00.000Z là khác nhau, mặc dù chúng đại diện cho cùng một thời điểm, vì các chuỗi khác nhau. Khóa trùng lặp. RFC 8259 §4 nêu rằng "các tên trong một đối tượng NÊN là duy nhất", SHOULD chuẩn tắc yếu hơn MUST. JSON.parse() của JavaScript chấp nhận các khóa trùng lặp và âm thầm giữ cái cuối cùng; các parser khác có thể giữ cái đầu tiên hoặc gây ra lỗi. null vs thiếu. {"a": null} và {} là các giá trị JSON khác nhau. Công cụ này báo cáo cái đầu tiên là "khóa a có giá trị null" và cái thứ hai là "không có khóa a", chúng được phân biệt chính xác. Việc xử lý null như một tín hiệu xóa của JSON Merge Patch là một sự thừa nhận cấp độ định dạng rằng sự phân biệt là có thật và phức tạp.
Hệ sinh thái vào năm 2026
Diff JSON mã nguồn mở bị thống trị bởi một số ít thư viện. jsondiffpatch của Benjamin Eidelman, lần đầu xuất bản vào năm 2012, là thư viện JavaScript de-facto: ~5k sao GitHub, hỗ trợ objectHash, mảng LCS, patch ngược và một bộ định dạng HTML trực quan. json-diff của Andrey Tarantsov là công cụ Node CLI kinh điển, với cùng tên được chia sẻ bởi các triển khai song song trong Python, Go và Rust. DeepDiff của Sep Dehpour là thư viện Python thống trị, diff đệ quy với các tùy chọn để bỏ qua thứ tự, bỏ qua các thay đổi kiểu số, và một loạt các điều khiển hạt mịn. Diffing JsonNode của Linux Foundation trong Jackson (Java) là lựa chọn JVM tiêu chuẩn. jq không có lệnh diff native nhưng họ toán tử = và // của nó có thể diễn đạt các so sánh cấu trúc đơn giản. Diff tích hợp của VS Code xử lý JSON qua chế độ văn bản; tiện ích mở rộng JSON Tools thêm so sánh nhận biết JSON. Các công cụ trực tuyến như Diffchecker cung cấp các chế độ JSON về cơ bản là cùng một walk đệ quy mà công cụ này triển khai, thường được phân lớp trên cùng các thư viện.
Tại sao chỉ-Trình duyệt Quan trọng ở đây
Diffing hai payload JSON trên một máy chủ yêu cầu tải lên cả hai. Đối với các ví dụ dữ liệu công khai bình thường, điều này là vô hại. Đối với các phản hồi API chứa token xác thực, PII khách hàng, hồ sơ nhân viên nội bộ, bí mật cấu hình hoặc dữ liệu sản phẩm chưa được phát hành, thì không. Ngay cả sau khi diff kết thúc, các payload đó vẫn ngồi trong nhật ký máy chủ, có thể trong bộ nhớ cache CDN, có thể trong một đường ống phân tích, có thể trong một bản sao lưu. Một diff chỉ-trình duyệt không bao giờ truyền, JSON được phân tích cú pháp và đi qua hoàn toàn trong tab của bạn. Bạn có thể xác minh bằng cách mở tab Network của DevTools khi bạn nhấp Compare; không có yêu cầu đi ra. Để so sánh các phản hồi API với một baseline, debug drift cấu hình giữa các môi trường, hoặc kiểm toán các thay đổi trong một lockfile có chứa các URL gói nội bộ, "JSON không bao giờ rời khỏi thiết bị của tôi" là kiến trúc làm cho công cụ an toàn để sử dụng trên dữ liệu sản xuất thực tế.
Câu hỏi thường gặp
Thứ tự của các khóa có ảnh hưởng đến so sánh không?
Không. Các đối tượng JSON không có thứ tự theo đặc tả, vì vậy {"a":1, "b":2} và {"b":2, "a":1} được xử lý như giống hệt. Chỉ các sự khác biệt thực sự về giá trị được báo cáo.
Các mảng được so sánh như thế nào?
Các mảng được so sánh theo chỉ số. Nếu mảng tại chỉ số 2 khác giữa hai đầu vào, chỉ số cụ thể này được báo cáo. Các phần tử được thêm hoặc xóa ở cuối mảng cũng được phát hiện.
Điều gì xảy ra với các đối tượng được lồng sâu?
So sánh hoàn toàn đệ quy. Các đối tượng và các mảng được lồng ở bất kỳ độ sâu nào được so sánh thuộc tính theo thuộc tính. Đầu ra hiển thị đường dẫn đầy đủ của mỗi sự khác biệt (vd: «user.address.city»).
Tại sao so sánh số nguyên lớn của tôi trông sai?
JavaScript phân tích mọi số JSON dưới dạng IEEE 754 double 64-bit. Trên Number.MAX_SAFE_INTEGER (253 − 1 = 9.007.199.254.740.991), các số nguyên có thể âm thầm mất độ chính xác. Vì vậy, một tệp JSON chứa 9007199254740993 có thể được so sánh là giống hệt với một tệp chứa 9007199254740992. Đây là một hạn chế của biểu diễn số cơ bản, không phải công cụ diff. Nếu bạn đang diff dữ liệu chứa các ID 64-bit (ID snowflake Twitter, ID Discord, các khóa cơ sở dữ liệu lớn), hãy yêu cầu bộ tuần tự hóa của bạn phát chúng dưới dạng chuỗi thay vì số. So sánh dấu chấm động có cùng cảnh báo: 0.1 + 0.2 nổi tiếng không chính xác 0.3 trong IEEE 754.
Tôi có thể so sánh JSON5 hoặc JSONC (JSON với bình luận) không?
Không trực tiếp. Công cụ này sử dụng JSON.parse() tích hợp của trình duyệt, theo RFC 8259 một cách nghiêm ngặt, bình luận, dấu phẩy trailing, chuỗi được trích dẫn đơn, và khóa không có dấu nháy là các lỗi cú pháp. Nếu đầu vào của bạn là JSON5 (json5.org) hoặc JSONC kiểu VS Code, hãy loại bỏ bình luận và dấu phẩy trailing trước, hoặc chạy nó qua một công cụ như jsonc-parser để chuyển đổi sang JSON nghiêm ngặt trước khi dán.
JSON của tôi có được gửi đến máy chủ không?
Không. So sánh chạy hoàn toàn trong trình duyệt của bạn qua JavaScript. JSON đã dán không bao giờ băng qua mạng, xác minh trong tab Network của DevTools khi bạn nhấp Compare, hoặc đưa trang offline sau khi nó tải và xác nhận diff vẫn hoạt động. An toàn để so sánh các phản hồi API với token xác thực, các tệp cấu hình với bí mật, hoặc các bản xuất cơ sở dữ liệu chứa PII khách hàng.