Compare JSON¶
Comparing two JSON documents side by side.
The app presents two text areas arranged in left and right columns. A user can paste a JSON string into each text area and click a Compare button. The script then attempts to parse, normalize, and pretty-format both JSON values, updates the displayed text areas with the formatted JSON, and reports whether the two JSON documents are equivalent.
import json
from typing import Any
import streamlit as st
MAX_DIFFS = 200
- load_json(raw_text: str) tuple[Any | None, str | None]¶
def load_json(raw_text: str) -> tuple[Any | None, str | None]:
if not raw_text.strip():
return None, "JSON input is empty."
try:
return json.loads(raw_text), None
except json.JSONDecodeError as exc:
return None, f"Invalid JSON at line {exc.lineno}, column {exc.colno}: {exc.msg}"
- format_json(value: Any) str¶
def format_json(value: Any) -> str:
return json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False)
- preview_value(value: Any) str¶
def preview_value(value: Any) -> str:
rendered = json.dumps(value, sort_keys=True, ensure_ascii=False)
return rendered if len(rendered) <= 140 else f"{rendered[:137]}..."
- path_join(path: str, part: str) str¶
def path_join(path: str, part: str) -> str:
return f"{path}.{part}" if path else part
- compare_json(left: Any, right: Any, path: str = '$') list[str]¶
def compare_json(left: Any, right: Any, path: str = "$") -> list[str]:
diffs: list[str] = []
def add(message: str) -> bool:
diffs.append(message)
return len(diffs) >= MAX_DIFFS
def walk(left_value: Any, right_value: Any, current_path: str) -> bool:
if type(left_value) is not type(right_value):
return add(
f"{current_path}: type differs "
f"({type(left_value).__name__} != {type(right_value).__name__})"
)
if isinstance(left_value, dict):
left_keys = set(left_value)
right_keys = set(right_value)
for key in sorted(left_keys - right_keys):
if add(f"{path_join(current_path, key)}: only in left"):
return True
for key in sorted(right_keys - left_keys):
if add(f"{path_join(current_path, key)}: only in right"):
return True
for key in sorted(left_keys & right_keys):
if walk(left_value[key], right_value[key], path_join(current_path, key)):
return True
return False
if isinstance(left_value, list):
common_length = min(len(left_value), len(right_value))
for index in range(common_length):
if walk(left_value[index], right_value[index], f"{current_path}[{index}]"):
return True
if len(left_value) > len(right_value):
for index in range(common_length, len(left_value)):
if add(f"{current_path}[{index}]: only in left"):
return True
if len(right_value) > len(left_value):
for index in range(common_length, len(right_value)):
if add(f"{current_path}[{index}]: only in right"):
return True
return False
if left_value != right_value:
return add(
f"{current_path}: value differs "
f"({preview_value(left_value)} != {preview_value(right_value)})"
)
return False
walk(left, right, path)
return diffs
- compare_inputs() None¶
def compare_inputs() -> None:
left_json, left_error = load_json(st.session_state.left_json)
right_json, right_error = load_json(st.session_state.right_json)
st.session_state.left_error = left_error
st.session_state.right_error = right_error
st.session_state.diffs = []
st.session_state.are_equal = False
if left_error or right_error:
return
st.session_state.left_json = format_json(left_json)
st.session_state.right_json = format_json(right_json)
st.session_state.diffs = compare_json(left_json, right_json)
st.session_state.are_equal = not st.session_state.diffs
st.set_page_config(page_title="JSON Compare", layout="wide")
st.title("JSON Compare")
if "left_json" not in st.session_state:
st.session_state.left_json = ""
if "right_json" not in st.session_state:
st.session_state.right_json = ""
if "left_error" not in st.session_state:
st.session_state.left_error = None
if "right_error" not in st.session_state:
st.session_state.right_error = None
if "diffs" not in st.session_state:
st.session_state.diffs = []
if "are_equal" not in st.session_state:
st.session_state.are_equal = False
left_column, right_column = st.columns(2)
with left_column:
st.text_area("Left JSON", key="left_json", height=520)
with right_column:
st.text_area("Right JSON", key="right_json", height=520)
st.button("Compare", type="primary", on_click=compare_inputs)
if st.session_state.left_error:
st.error(f"Left JSON: {st.session_state.left_error}")
if st.session_state.right_error:
st.error(f"Right JSON: {st.session_state.right_error}")
if not st.session_state.left_error and not st.session_state.right_error:
if st.session_state.are_equal:
st.success("JSON documents are equal after normalization.")
elif st.session_state.diffs:
st.warning(f"JSON documents differ. Showing up to {MAX_DIFFS} differences.")
st.code("\n".join(st.session_state.diffs), language="text")