{"id":83,"date":"2026-03-18T12:41:04","date_gmt":"2026-03-18T12:41:04","guid":{"rendered":"https:\/\/tools.sanepo.com\/?post_type=tool&#038;p=83"},"modified":"2026-03-18T12:41:05","modified_gmt":"2026-03-18T12:41:05","slug":"json-diff-tool","status":"publish","type":"tool","link":"https:\/\/tools.sanepo.com\/zh-hans\/features\/json-diff-tool\/","title":{"rendered":"Free Advanced JSON Diff Tool \u2013 Compare &amp; Analyze JSON Online"},"content":{"rendered":"\n<div id=\"wpnt-json-diff\">\n    <!-- Header -->\n    <div class=\"wpnt-header\">\n        <h3>Sanepo Tools &#8211; JSON Diff<\/h3>\n        <p>Advanced JSON comparison tool. Deep compare, validate, and analyze structures instantly.<\/p>\n        <span class=\"wpnt-privacy-badge\">\ud83d\udd12 100% Secure Local Processing<\/span>\n    <\/div>\n\n    <!-- Advanced Settings Grid -->\n    <div class=\"wpnt-settings-panel\">\n        <div class=\"wpnt-label\">Comparison Settings<\/div>\n        <div class=\"wpnt-settings-grid\">\n            <label class=\"wpnt-checkbox-label\" title=\"Check exact data types (e.g., 1 vs '1')\">\n                <input type=\"checkbox\" id=\"setting-strict\" checked>\n                <span class=\"wpnt-checkbox-custom\"><\/span>\n                Strict Type Checking\n            <\/label>\n            <label class=\"wpnt-checkbox-label\" title=\"Ignore uppercase\/lowercase differences in strings\">\n                <input type=\"checkbox\" id=\"setting-ignore-case\">\n                <span class=\"wpnt-checkbox-custom\"><\/span>\n                Ignore Case (Strings)\n            <\/label>\n        <\/div>\n    <\/div>\n\n    <!-- Input Section -->\n    <div class=\"wpnt-json-inputs\">\n        <!-- Original JSON -->\n        <div class=\"wpnt-input-group\">\n            <div class=\"wpnt-toolbar\">\n                <span class=\"wpnt-toolbar-title\">Original JSON<\/span>\n                <div class=\"wpnt-toolbar-actions\">\n                    <button class=\"wpnt-btn wpnt-btn-tiny\" id=\"btn-sample-left\">Sample<\/button>\n                    <button class=\"wpnt-btn wpnt-btn-tiny wpnt-btn-danger\" id=\"btn-clear-left\">Clear<\/button>\n                    <button class=\"wpnt-btn wpnt-btn-tiny wpnt-btn-accent\" id=\"btn-format-left\">Format<\/button>\n                <\/div>\n            <\/div>\n            <textarea id=\"json-left\" placeholder='{\"example\": \"Paste original JSON here...\"}' spellcheck=\"false\"><\/textarea>\n            <div id=\"error-left\" class=\"wpnt-error-msg\"><\/div>\n        <\/div>\n\n        <!-- Swap Button -->\n        <div class=\"wpnt-swap-container\">\n            <button class=\"wpnt-btn-icon\" id=\"btn-swap\" title=\"Swap JSONs\">\n                <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m3 16 4 4 4-4\"\/><path d=\"M7 20V4\"\/><path d=\"m21 8-4-4-4 4\"\/><path d=\"M17 4v16\"\/><\/svg>\n            <\/button>\n        <\/div>\n\n        <!-- Modified JSON -->\n        <div class=\"wpnt-input-group\">\n            <div class=\"wpnt-toolbar\">\n                <span class=\"wpnt-toolbar-title\">Modified JSON<\/span>\n                <div class=\"wpnt-toolbar-actions\">\n                    <button class=\"wpnt-btn wpnt-btn-tiny\" id=\"btn-sample-right\">Sample<\/button>\n                    <button class=\"wpnt-btn wpnt-btn-tiny wpnt-btn-danger\" id=\"btn-clear-right\">Clear<\/button>\n                    <button class=\"wpnt-btn wpnt-btn-tiny wpnt-btn-accent\" id=\"btn-format-right\">Format<\/button>\n                <\/div>\n            <\/div>\n            <textarea id=\"json-right\" placeholder='{\"example\": \"Paste modified JSON here...\"}' spellcheck=\"false\"><\/textarea>\n            <div id=\"error-right\" class=\"wpnt-error-msg\"><\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Action Button -->\n    <button id=\"btn-compare\" class=\"wpnt-btn wpnt-btn-main\">\n        <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 3h5v5\"\/><path d=\"M8 3H3v5\"\/><path d=\"M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3\"\/><path d=\"m15 9 6-6\"\/><\/svg>\n        Execute Deep Compare\n    <\/button>\n\n    <!-- Results Section -->\n    <div id=\"diff-results\" class=\"wpnt-results\">\n        <div class=\"wpnt-results-header\">\n            <div>\n                <div class=\"wpnt-label\">Comparison Result<\/div>\n                <div class=\"wpnt-legend\">\n                    <span class=\"legend-item legend-added\">Added<\/span>\n                    <span class=\"legend-item legend-removed\">Removed<\/span>\n                    <span class=\"legend-item legend-changed\">Changed<\/span>\n                    <span class=\"legend-item legend-unchanged\">Unchanged<\/span>\n                <\/div>\n            <\/div>\n            <div class=\"wpnt-results-actions\">\n                <button class=\"wpnt-btn wpnt-btn-tiny\" id=\"btn-expand-all\">Expand All<\/button>\n                <button class=\"wpnt-btn wpnt-btn-tiny\" id=\"btn-collapse-all\">Collapse All<\/button>\n                <button class=\"wpnt-btn wpnt-btn-tiny wpnt-btn-accent\" id=\"btn-copy-diff\">Copy Text<\/button>\n            <\/div>\n        <\/div>\n        \n        <div class=\"wpnt-diff-viewer-wrapper\">\n            <div id=\"diff-viewer\" class=\"wpnt-diff-viewer\"><\/div>\n        <\/div>\n    <\/div>\n\n    <style>\n        @import url('https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap');\n\n        \/* CSS Variables Default *\/\n        #wpnt-json-diff {\n            --bg-primary: #ffffff;\n            --bg-secondary: #f8fafc;\n            --border-color: #e2e8f0;\n            --accent-color: #4361ee;\n            --accent-hover: #3a56d4;\n            --glass-bg: rgba(255, 255, 255, 0.85);\n            --card-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);\n            --transition: all 0.2s ease;\n            \n            --wpnt-success: #10b981;\n            --wpnt-danger: #ef4444;\n            --wpnt-warning: #f59e0b;\n            --wpnt-radius-lg: 16px;\n            --wpnt-radius-md: 12px;\n            --wpnt-radius-sm: 8px;\n            \n            --diff-added-bg: rgba(16, 185, 129, 0.12);\n            --diff-added-text: #059669;\n            --diff-removed-bg: rgba(239, 68, 68, 0.12);\n            --diff-removed-text: #dc2626;\n            --diff-changed-bg: rgba(245, 158, 11, 0.12);\n            --diff-changed-text: #d97706;\n            --diff-unchanged-text: #64748b;\n            --diff-key-color: #0f172a;\n            \n            --text-main: #1e293b;\n            \n            font-family: 'Inter', sans-serif;\n            color: var(--text-main) !important;\n            width: 100%;\n            margin: 30px auto;\n            background: transparent;\n            border: none;\n            border-radius: 0;\n            box-shadow: none;\n            padding: 0;\n            box-sizing: border-box;\n            line-height: 1.5;\n        }\n\n        #wpnt-json-diff * { box-sizing: border-box; }\n\n        \/* Sinkronisasi Dark Mode dengan Class Website (Prioritas Utama) *\/\n        html.dark #wpnt-json-diff,\n        body.dark #wpnt-json-diff,\n        body.dark-mode #wpnt-json-diff,\n        [data-theme=\"dark\"] #wpnt-json-diff {\n            --bg-primary: #0f172a;\n            --bg-secondary: #1e293b;\n            --border-color: #334155;\n            --glass-bg: rgba(15, 23, 42, 0.7);\n            --text-main: #f1f5f9;\n            \n            --diff-added-bg: rgba(16, 185, 129, 0.2);\n            --diff-added-text: #34d399;\n            --diff-removed-bg: rgba(239, 68, 68, 0.2);\n            --diff-removed-text: #f87171;\n            --diff-changed-bg: rgba(245, 158, 11, 0.2);\n            --diff-changed-text: #fbbf24;\n            --diff-unchanged-text: #94a3b8;\n            --diff-key-color: #e2e8f0;\n        }\n\n        \/* Fallback OS Dark Mode HANYA jika website tidak diset manual ke Light Mode *\/\n        @media (prefers-color-scheme: dark) {\n            html:not(.light):not([data-theme=\"light\"]) #wpnt-json-diff {\n                --bg-primary: #0f172a;\n                --bg-secondary: #1e293b;\n                --border-color: #334155;\n                --glass-bg: rgba(15, 23, 42, 0.7);\n                --text-main: #f1f5f9;\n                \n                --diff-added-bg: rgba(16, 185, 129, 0.2);\n                --diff-added-text: #34d399;\n                --diff-removed-bg: rgba(239, 68, 68, 0.2);\n                --diff-removed-text: #f87171;\n                --diff-changed-bg: rgba(245, 158, 11, 0.2);\n                --diff-changed-text: #fbbf24;\n                --diff-unchanged-text: #94a3b8;\n                --diff-key-color: #e2e8f0;\n            }\n        }\n\n        \/* Basic Elements *\/\n        #wpnt-json-diff .wpnt-header { text-align: center; margin-bottom: 24px; }\n        #wpnt-json-diff h3 { margin: 0 0 8px 0; font-size: 1.75rem; font-weight: 700; letter-spacing: -0.025em; }\n        #wpnt-json-diff p { margin: 0; font-size: 0.95rem; opacity: 0.8; }\n        #wpnt-json-diff .wpnt-privacy-badge {\n            display: inline-block; background: rgba(16, 185, 129, 0.1); color: var(--wpnt-success);\n            border: 1px solid rgba(16, 185, 129, 0.2); padding: 6px 12px; border-radius: 20px;\n            font-size: 0.8rem; font-weight: 600; margin-top: 12px;\n        }\n\n        #wpnt-json-diff .wpnt-label {\n            font-weight: 700; font-size: 0.8rem; text-transform: uppercase;\n            letter-spacing: 0.05em; opacity: 0.7; margin-bottom: 8px;\n        }\n\n        \/* Settings Panel *\/\n        #wpnt-json-diff .wpnt-settings-panel {\n            background-color: var(--bg-primary) !important;\n            color: var(--text-main) !important;\n            padding: 16px 20px; border-radius: var(--wpnt-radius-md);\n            border: 1px solid var(--border-color); margin-bottom: 24px; box-shadow: var(--card-shadow);\n        }\n        #wpnt-json-diff .wpnt-settings-grid {\n            display: flex; gap: 24px; flex-wrap: wrap;\n        }\n        #wpnt-json-diff .wpnt-checkbox-label {\n            display: flex; align-items: center; gap: 8px; font-size: 0.9rem;\n            cursor: pointer; user-select: none; font-weight: 500;\n        }\n        #wpnt-json-diff .wpnt-checkbox-label input { display: none; }\n        #wpnt-json-diff .wpnt-checkbox-custom {\n            width: 18px; height: 18px; border: 2px solid var(--border-color);\n            border-radius: 4px; display: inline-flex; align-items: center; justify-content: center;\n            transition: var(--transition); background-color: var(--bg-primary) !important;\n        }\n        #wpnt-json-diff .wpnt-checkbox-label input:checked + .wpnt-checkbox-custom {\n            background-color: var(--accent-color) !important; border-color: var(--accent-color);\n        }\n        #wpnt-json-diff .wpnt-checkbox-label input:checked + .wpnt-checkbox-custom::after {\n            content: \"\u2713\"; color: white; font-size: 12px; font-weight: bold;\n        }\n\n        \/* Layout Grid untuk Input *\/\n        #wpnt-json-diff .wpnt-json-inputs {\n            display: grid; grid-template-columns: 1fr auto 1fr; gap: 16px;\n            align-items: stretch; margin-bottom: 24px;\n        }\n        #wpnt-json-diff .wpnt-input-group { display: flex; flex-direction: column; width: 100%; box-shadow: var(--card-shadow); border-radius: var(--wpnt-radius-md); background-color: var(--bg-primary); }\n\n        \/* Toolbar *\/\n        #wpnt-json-diff .wpnt-toolbar {\n            display: flex; justify-content: space-between; align-items: center;\n            background-color: var(--bg-secondary) !important;\n            color: var(--text-main) !important;\n            padding: 8px 12px;\n            border: 1px solid var(--border-color); border-bottom: none;\n            border-radius: var(--wpnt-radius-md) var(--wpnt-radius-md) 0 0;\n        }\n        #wpnt-json-diff .wpnt-toolbar-title { font-weight: 600; font-size: 0.85rem; }\n        #wpnt-json-diff .wpnt-toolbar-actions { display: flex; gap: 6px; }\n\n        \/* Textareas *\/\n        #wpnt-json-diff textarea {\n            width: 100%; flex-grow: 1; min-height: 350px; padding: 16px;\n            border-radius: 0 0 var(--wpnt-radius-md) var(--wpnt-radius-md);\n            border: 1px solid var(--border-color); \n            background-color: var(--bg-primary) !important;\n            color: var(--text-main) !important;\n            font-family: 'Fira Code', monospace;\n            font-size: 0.85rem; resize: vertical; outline: none; line-height: 1.5;\n        }\n        #wpnt-json-diff textarea:focus {\n            border-color: var(--accent-color); z-index: 1; position: relative;\n        }\n\n        #wpnt-json-diff .wpnt-error-msg {\n            color: var(--wpnt-danger); font-size: 0.8rem; font-weight: 500;\n            margin-top: 8px; min-height: 20px;\n        }\n\n        \/* Buttons *\/\n        #wpnt-json-diff .wpnt-btn {\n            background: var(--bg-primary); color: var(--accent-color);\n            border: 1px solid var(--border-color); border-radius: var(--wpnt-radius-sm);\n            cursor: pointer; font-weight: 500; display: inline-flex;\n            align-items: center; justify-content: center; gap: 8px; transition: var(--transition);\n        }\n        #wpnt-json-diff .wpnt-btn:hover { border-color: var(--accent-color); }\n        \n        #wpnt-json-diff .wpnt-btn-tiny { padding: 4px 10px; font-size: 0.75rem; }\n        #wpnt-json-diff .wpnt-btn-danger:hover { background: var(--wpnt-danger); color: white; border-color: var(--wpnt-danger); }\n        #wpnt-json-diff .wpnt-btn-accent { background: rgba(67, 97, 238, 0.1); border-color: transparent; }\n        #wpnt-json-diff .wpnt-btn-accent:hover { background: var(--accent-color); color: white; }\n        \n        #wpnt-json-diff .wpnt-btn-main {\n            background: var(--accent-color); color: #ffffff; padding: 16px 24px;\n            width: 100%; font-size: 1.1rem; font-weight: 600; border: none;\n            box-shadow: var(--card-shadow);\n        }\n        #wpnt-json-diff .wpnt-btn-main:hover {\n            background: var(--accent-hover); transform: translateY(-2px);\n            box-shadow: 0 4px 12px rgba(67, 97, 238, 0.25);\n        }\n\n        #wpnt-json-diff .wpnt-btn-icon {\n            background-color: var(--bg-primary) !important; color: var(--accent-color);\n            border: 1px solid var(--border-color); border-radius: 50%;\n            width: 48px; height: 48px; display: flex; align-items: center;\n            justify-content: center; cursor: pointer; box-shadow: var(--card-shadow);\n            margin-top: 30px; \/* offset for toolbar *\/\n        }\n        #wpnt-json-diff .wpnt-btn-icon:hover { transform: translateY(-2px); border-color: var(--accent-color); }\n\n        \/* Results & Diff Viewer *\/\n        #wpnt-json-diff .wpnt-results {\n            display: none; margin-top: 32px; \n            background-color: var(--bg-primary) !important;\n            color: var(--text-main) !important;\n            border-radius: var(--wpnt-radius-md); border: 1px solid var(--border-color);\n            overflow: hidden; box-shadow: var(--card-shadow);\n        }\n        #wpnt-json-diff .wpnt-results-header {\n            padding: 20px 24px; border-bottom: 1px solid var(--border-color);\n            display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 16px;\n        }\n        #wpnt-json-diff .wpnt-results-actions { display: flex; gap: 8px; }\n\n        #wpnt-json-diff .wpnt-legend { display: flex; gap: 12px; flex-wrap: wrap; }\n        #wpnt-json-diff .legend-item { font-size: 0.75rem; font-weight: 600; padding: 4px 10px; border-radius: 4px; }\n        #wpnt-json-diff .legend-added { background: var(--diff-added-bg); color: var(--diff-added-text); }\n        #wpnt-json-diff .legend-removed { background: var(--diff-removed-bg); color: var(--diff-removed-text); }\n        #wpnt-json-diff .legend-changed { background: var(--diff-changed-bg); color: var(--diff-changed-text); }\n        #wpnt-json-diff .legend-unchanged { color: var(--diff-unchanged-text); border: 1px solid var(--border-color); }\n\n        \/* Advanced Tree Viewer CSS *\/\n        #wpnt-json-diff .wpnt-diff-viewer-wrapper { padding: 20px; background-color: var(--bg-primary) !important; overflow-x: auto; }\n        #wpnt-json-diff .wpnt-diff-viewer {\n            font-family: 'Fira Code', monospace; font-size: 0.85rem; line-height: 1.6;\n        }\n\n        \/* Details & Summary Styling for Collapsible Nodes *\/\n        #wpnt-json-diff details { padding-left: 20px; position: relative; }\n        #wpnt-json-diff summary {\n            display: block; cursor: pointer; position: relative; margin-left: -20px; padding-left: 20px; outline: none;\n        }\n        #wpnt-json-diff summary::-webkit-details-marker { display: none; } \/* Hide default arrow *\/\n        #wpnt-json-diff summary::before {\n            content: '\u25b6'; position: absolute; left: 4px; top: 0; font-size: 0.7rem;\n            color: var(--accent-color); transition: transform 0.2s; opacity: 0.7;\n        }\n        #wpnt-json-diff details[open] > summary::before { transform: rotate(90deg); }\n        #wpnt-json-diff summary:hover::before { opacity: 1; }\n\n        \/* Lines *\/\n        #wpnt-json-diff .diff-line { display: block; padding: 0 4px; border-radius: 2px; position: relative; }\n        #wpnt-json-diff .diff-line.added { background: var(--diff-added-bg); }\n        #wpnt-json-diff .diff-line.removed { background: var(--diff-removed-bg); }\n        #wpnt-json-diff .diff-line.changed { background: var(--diff-changed-bg); }\n        \n        #wpnt-json-diff .diff-line::before {\n            content: ''; position: absolute; left: -16px; width: 12px; text-align: right;\n            font-weight: bold; font-size: 0.8rem; top: 0;\n        }\n        #wpnt-json-diff .diff-line.added::before { content: '+'; color: var(--diff-added-text); }\n        #wpnt-json-diff .diff-line.removed::before { content: '-'; color: var(--diff-removed-text); }\n        #wpnt-json-diff .diff-line.changed::before { content: '~'; color: var(--diff-changed-text); }\n\n        \/* Value Coloring *\/\n        #wpnt-json-diff .diff-key { color: var(--diff-key-color); font-weight: 600; }\n        #wpnt-json-diff .diff-val-string { color: #16a34a; }\n        #wpnt-json-diff .diff-val-number { color: #ea580c; }\n        #wpnt-json-diff .diff-val-boolean { color: #2563eb; font-weight: 600;}\n        #wpnt-json-diff .diff-val-null { color: #94a3b8; font-style: italic;}\n        \n        #wpnt-json-diff .diff-val-added { color: var(--diff-added-text); font-weight: 600; }\n        #wpnt-json-diff .diff-val-removed { color: var(--diff-removed-text); text-decoration: line-through; margin-right: 8px; opacity: 0.8;}\n        #wpnt-json-diff .diff-val-changed { color: var(--diff-changed-text); font-weight: 600; }\n        #wpnt-json-diff .diff-bracket { color: var(--diff-unchanged-text); font-weight: bold;}\n\n        \/* Dark Mode Syntax Adjustment *\/\n        @media (prefers-color-scheme: dark) {\n            #wpnt-json-diff .diff-val-string { color: #4ade80; }\n            #wpnt-json-diff .diff-val-number { color: #fb923c; }\n            #wpnt-json-diff .diff-val-boolean { color: #60a5fa; }\n        }\n\n        \/* Responsiveness *\/\n        @media (max-width: 850px) {\n            #wpnt-json-diff { padding: 10px 0; }\n            #wpnt-json-diff .wpnt-json-inputs { grid-template-columns: 1fr; }\n            #wpnt-json-diff .wpnt-swap-container {\n                display: flex; justify-content: center; transform: rotate(90deg); margin: -8px 0;\n            }\n            #wpnt-json-diff textarea { min-height: 250px; }\n        }\n    <\/style>\n\n    <script>\n        \/**\n         * WP-NanoTech Advanced JSON Diff Logic\n         * Features: Collapsible Tree, Type Checking, Case Ignoring, Sample Data.\n         *\/\n        (() => {\n            \/\/ DOM Elements\n            const elLeft = document.getElementById('json-left');\n            const elRight = document.getElementById('json-right');\n            const errLeft = document.getElementById('error-left');\n            const errRight = document.getElementById('error-right');\n            const btnCompare = document.getElementById('btn-compare');\n            const btnSwap = document.getElementById('btn-swap');\n            const diffResults = document.getElementById('diff-results');\n            const diffViewer = document.getElementById('diff-viewer');\n\n            \/\/ Sample Data\n            const sampleOriginal = {\n                \"project\": \"Sanepo Tools\",\n                \"version\": 1.0,\n                \"status\": \"active\",\n                \"features\": [\"format\", \"compare\"],\n                \"settings\": { \"theme\": \"light\", \"notifications\": true },\n                \"metadata\": null\n            };\n            const sampleModified = {\n                \"project\": \"Sanepo Tools PRO\",\n                \"version\": \"1.1\",\n                \"status\": \"active\",\n                \"features\": [\"format\", \"compare\", \"tree-view\"],\n                \"settings\": { \"theme\": \"dark\", \"autoSave\": true },\n                \"author\": \"WP-NanoTech\"\n            };\n\n            \/\/ Toolbar Actions\n            document.getElementById('btn-sample-left').addEventListener('click', () => { elLeft.value = JSON.stringify(sampleOriginal, null, 2); errLeft.textContent='';});\n            document.getElementById('btn-sample-right').addEventListener('click', () => { elRight.value = JSON.stringify(sampleModified, null, 2); errRight.textContent=''; });\n            document.getElementById('btn-clear-left').addEventListener('click', () => { elLeft.value = ''; errLeft.textContent=''; });\n            document.getElementById('btn-clear-right').addEventListener('click', () => { elRight.value = ''; errRight.textContent=''; });\n\n            \/\/ Format & Validate\n            const formatAndValidateJSON = (textareaEl, errorEl) => {\n                const rawVal = textareaEl.value.trim();\n                errorEl.textContent = '';\n                if (!rawVal) return null;\n                try {\n                    const parsed = JSON.parse(rawVal);\n                    textareaEl.value = JSON.stringify(parsed, null, 2);\n                    return parsed;\n                } catch (e) {\n                    errorEl.textContent = \"Invalid JSON: \" + e.message;\n                    return null;\n                }\n            };\n\n            document.getElementById('btn-format-left').addEventListener('click', () => formatAndValidateJSON(elLeft, errLeft));\n            document.getElementById('btn-format-right').addEventListener('click', () => formatAndValidateJSON(elRight, errRight));\n\n            btnSwap.addEventListener('click', () => {\n                const temp = elLeft.value; elLeft.value = elRight.value; elRight.value = temp;\n                errLeft.textContent = ''; errRight.textContent = '';\n            });\n\n            \/\/ Tree View Actions\n            document.getElementById('btn-expand-all').addEventListener('click', () => {\n                document.querySelectorAll('#diff-viewer details').forEach(d => d.setAttribute('open', 'true'));\n            });\n            document.getElementById('btn-collapse-all').addEventListener('click', () => {\n                document.querySelectorAll('#diff-viewer details').forEach(d => d.removeAttribute('open'));\n            });\n            document.getElementById('btn-copy-diff').addEventListener('click', function() {\n                \/\/ Copy plain text by stripping HTML tags\n                const plainText = diffViewer.innerText;\n                navigator.clipboard.writeText(plainText).then(() => {\n                    const originalText = this.innerText;\n                    this.innerText = 'Copied!';\n                    setTimeout(() => this.innerText = originalText, 2000);\n                });\n            });\n\n            \/\/ Syntax Highlighting Helper\n            const highlightVal = (val) => {\n                if (typeof val === 'string') return `<span class=\"diff-val-string\">\"${val}\"<\/span>`;\n                if (typeof val === 'number') return `<span class=\"diff-val-number\">${val}<\/span>`;\n                if (typeof val === 'boolean') return `<span class=\"diff-val-boolean\">${val}<\/span>`;\n                if (val === null) return `<span class=\"diff-val-null\">null<\/span>`;\n                return val;\n            };\n\n            \/\/ Raw formatting for changed\/removed rendering\n            const formatRaw = (val) => {\n                if (typeof val === 'string') return `\"${val}\"`;\n                if (val === null) return 'null';\n                if (typeof val === 'object') return JSON.stringify(val);\n                return val;\n            };\n\n            \/\/ Deep Compare Logic\n            const deepCompare = (obj1, obj2, isStrict, ignoreCase) => {\n                const isObj = (val) => val !== null && typeof val === 'object';\n                \n                if (!isObj(obj1) || !isObj(obj2)) {\n                    \/\/ Primitive comparison based on settings\n                    let v1 = obj1; let v2 = obj2;\n                    if (ignoreCase && typeof v1 === 'string' && typeof v2 === 'string') {\n                        v1 = v1.toLowerCase(); v2 = v2.toLowerCase();\n                    }\n                    const isEqual = isStrict ? v1 === v2 : v1 == v2;\n\n                    if (isEqual) return { type: 'unchanged', value: obj1 };\n                    return { type: 'changed', oldVal: obj1, newVal: obj2 };\n                }\n\n                const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);\n                const result = Array.isArray(obj1) && Array.isArray(obj2) ? [] : {};\n                let hasChanges = false;\n\n                keys.forEach(key => {\n                    const has1 = obj1.hasOwnProperty(key);\n                    const has2 = obj2.hasOwnProperty(key);\n\n                    if (has1 && !has2) {\n                        result[key] = { type: 'removed', value: obj1[key] };\n                        hasChanges = true;\n                    } else if (!has1 && has2) {\n                        result[key] = { type: 'added', value: obj2[key] };\n                        hasChanges = true;\n                    } else {\n                        const comp = deepCompare(obj1[key], obj2[key], isStrict, ignoreCase);\n                        result[key] = comp;\n                        if (comp.type !== 'unchanged') hasChanges = true;\n                    }\n                });\n\n                \/\/ Return nested object wrapper\n                return { type: 'nested', value: result, isArray: Array.isArray(result), hasChanges: hasChanges };\n            };\n\n            \/\/ HTML Generator for Diff Tree\n            const renderDiffTreeHTML = (diffData, keyName = null, isLast = true) => {\n                let html = '';\n                const comma = isLast ? '' : ',';\n                const keyStr = keyName !== null ? `<span class=\"diff-key\">\"${keyName}\"<\/span>: ` : '';\n\n                if (diffData.type === 'unchanged') {\n                    html += `<div class=\"diff-line\">${keyStr}${highlightVal(diffData.value)}${comma}<\/div>`;\n                } else if (diffData.type === 'added') {\n                    html += `<div class=\"diff-line added\">${keyStr}<span class=\"diff-val-added\">${formatRaw(diffData.value)}<\/span>${comma}<\/div>`;\n                } else if (diffData.type === 'removed') {\n                    html += `<div class=\"diff-line removed\">${keyStr}<span class=\"diff-val-removed\">${formatRaw(diffData.value)}<\/span>${comma}<\/div>`;\n                } else if (diffData.type === 'changed') {\n                    html += `<div class=\"diff-line changed\">${keyStr}<span class=\"diff-val-removed\">${formatRaw(diffData.oldVal)}<\/span><span class=\"diff-val-changed\">${formatRaw(diffData.newVal)}<\/span>${comma}<\/div>`;\n                } else if (diffData.type === 'nested') {\n                    const openBracket = diffData.isArray ? '[' : '{';\n                    const closeBracket = diffData.isArray ? ']' : '}';\n                    const objKeys = Object.keys(diffData.value);\n                    \n                    if (objKeys.length === 0) {\n                        \/\/ Empty object\/array\n                        html += `<div class=\"diff-line\">${keyStr}<span class=\"diff-bracket\">${openBracket}${closeBracket}<\/span>${comma}<\/div>`;\n                    } else {\n                        \/\/ Use <details> for collapsible hierarchy. Auto-open if it has changes inside.\n                        const openAttr = diffData.hasChanges ? 'open' : '';\n                        html += `<details ${openAttr}>`;\n                        html += `<summary class=\"diff-line\">${keyStr}<span class=\"diff-bracket\">${openBracket}<\/span><\/summary>`;\n                        \n                        objKeys.forEach((k, index) => {\n                            const isLastChild = index === objKeys.length - 1;\n                            html += renderDiffTreeHTML(diffData.value[k], diffData.isArray ? null : k, isLastChild);\n                        });\n                        \n                        html += `<div class=\"diff-line\"><span class=\"diff-bracket\">${closeBracket}<\/span>${comma}<\/div>`;\n                        html += `<\/details>`;\n                    }\n                }\n                return html;\n            };\n\n            \/\/ Compare Execution\n            btnCompare.addEventListener('click', () => {\n                const objLeft = formatAndValidateJSON(elLeft, errLeft);\n                const objRight = formatAndValidateJSON(elRight, errRight);\n                \n                if (!objLeft || !objRight) {\n                    diffResults.style.display = 'none';\n                    return;\n                }\n\n                \/\/ Get Settings\n                const isStrict = document.getElementById('setting-strict').checked;\n                const ignoreCase = document.getElementById('setting-ignore-case').checked;\n\n                \/\/ Perform Diff\n                const diffData = deepCompare(objLeft, objRight, isStrict, ignoreCase);\n                \n                \/\/ Root is always a nested object\/array\n                let outputHtml = renderDiffTreeHTML(diffData, null, true);\n\n                diffViewer.innerHTML = outputHtml;\n                diffResults.style.display = 'block';\n\n                \/\/ Ensure parent <details> of root is not collapsible\n                const rootDetails = diffViewer.querySelector('details');\n                if(rootDetails) {\n                    rootDetails.setAttribute('open', 'true');\n                    \/\/ Optional: remove summary arrow for root if you want it permanently open\n                    rootDetails.querySelector('summary').style.pointerEvents = 'none';\n                    rootDetails.querySelector('summary').style.marginLeft = '0';\n                    rootDetails.querySelector('summary').style.paddingLeft = '0';\n                    rootDetails.querySelector('summary').style.listStyle = 'none';\n                }\n\n                diffResults.scrollIntoView({ behavior: 'smooth', block: 'start' });\n            });\n\n        })();\n    <\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Sanepo Tools &#8211; JSON Diff Advanced JSON comparison [&hellip;]<\/p>\n","protected":false},"featured_media":0,"template":"","meta":[],"class_list":["post-83","tool","type-tool","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/tools.sanepo.com\/zh-hans\/wp-json\/wp\/v2\/tool\/83","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tools.sanepo.com\/zh-hans\/wp-json\/wp\/v2\/tool"}],"about":[{"href":"https:\/\/tools.sanepo.com\/zh-hans\/wp-json\/wp\/v2\/types\/tool"}],"wp:attachment":[{"href":"https:\/\/tools.sanepo.com\/zh-hans\/wp-json\/wp\/v2\/media?parent=83"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}