|
|
from __future__ import annotations |
|
|
|
|
|
import json |
|
|
from pathlib import Path |
|
|
|
|
|
import gradio as gr |
|
|
|
|
|
from json_semval.pipeline import run_validation |
|
|
|
|
|
|
|
|
def load_fixtures() -> tuple[str, str]: |
|
|
try: |
|
|
schema = Path("tests/fixtures/sample_schema.json").read_text(encoding="utf-8") |
|
|
bad = Path("tests/fixtures/sample_bad.json").read_text(encoding="utf-8") |
|
|
return schema, bad |
|
|
except Exception: |
|
|
example_schema = '{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}' |
|
|
example_json = '{"name":"Alice"}' |
|
|
return example_schema, example_json |
|
|
|
|
|
|
|
|
def infer( |
|
|
schema_text: str, json_text: str, backend: str, apply_minimal: bool |
|
|
) -> tuple[str, str, str]: |
|
|
try: |
|
|
schema = json.loads(schema_text) |
|
|
payload = json.loads(json_text) |
|
|
except Exception as e: |
|
|
return "[]", f"Invalid input JSON: {e}", json_text |
|
|
report = run_validation(schema, payload, apply_fixes=apply_minimal, backend=backend) |
|
|
rule_errors = json.dumps(report.get("rule_errors", []), indent=2) |
|
|
ml_preds = json.dumps(report.get("ml_predictions", []), indent=2) |
|
|
corrected = json.dumps(report.get("corrected_json", payload), indent=2) |
|
|
return rule_errors, ml_preds, corrected |
|
|
|
|
|
|
|
|
EXAMPLES = { |
|
|
"Example 1: Fixtures (bad)": ( |
|
|
( |
|
|
Path("tests/fixtures/sample_schema.json").read_text(encoding="utf-8") |
|
|
if Path("tests/fixtures/sample_schema.json").exists() |
|
|
else '{"type":"object","properties":{"age":{"type":"integer"},"start_date":{"type":"string","format":"date"},"active":{"type":"boolean"},"status":{"type":"string","enum":["pending","approved","rejected"]}},"required":["age","start_date","active","status"]}' |
|
|
), |
|
|
( |
|
|
Path("tests/fixtures/sample_bad.json").read_text(encoding="utf-8") |
|
|
if Path("tests/fixtures/sample_bad.json").exists() |
|
|
else '{"age":"twenty five","active":"yes","start_date":"15 Jan 2024","status":"pendng"}' |
|
|
), |
|
|
), |
|
|
"Example 2: Minimal valid": ( |
|
|
'{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}', |
|
|
'{"name":"Alice"}', |
|
|
), |
|
|
"Example 3: Enum/date mix": ( |
|
|
'{"type":"object","properties":{"status":{"type":"string","enum":["pending","approved","rejected"]},"d":{"type":"string","format":"date"}},"required":["status","d"]}', |
|
|
'{"status":"Pendng","d":"01/02/2024"}', |
|
|
), |
|
|
} |
|
|
|
|
|
with gr.Blocks(title="JSON Semantic Validator") as demo: |
|
|
gr.Markdown( |
|
|
""" |
|
|
# JSON Semantic Validator |
|
|
Hybrid rules + tiny ML to validate and auto-fix JSON against a schema. |
|
|
""" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
schema_in = gr.Code(label="JSON Schema", language="json") |
|
|
json_in = gr.Code(label="JSON Payload", language="json") |
|
|
|
|
|
with gr.Row(): |
|
|
backend = gr.Dropdown( |
|
|
["rules-only", "local", "onnx"], value="rules-only", label="Backend" |
|
|
) |
|
|
apply_minimal = gr.Checkbox(value=True, label="Apply minimal fixes") |
|
|
example_dd = gr.Dropdown(list(EXAMPLES.keys()), label="Load Example") |
|
|
load_btn = gr.Button("Load Fixtures") |
|
|
run_btn = gr.Button("Run Validation") |
|
|
|
|
|
with gr.Row(): |
|
|
rule_errors_out = gr.Code(label="Rule Errors", language="json") |
|
|
ml_preds_out = gr.Code(label="ML Predictions", language="json") |
|
|
corrected_out = gr.Code(label="Corrected JSON", language="json") |
|
|
validity_md = gr.Markdown(visible=True) |
|
|
|
|
|
def _backend_value(b: str) -> str: |
|
|
if b == "rules-only": |
|
|
return "local" |
|
|
return b |
|
|
|
|
|
def on_load() -> tuple[str, str]: |
|
|
return load_fixtures() |
|
|
|
|
|
def on_example_select(label: str) -> tuple[str, str]: |
|
|
pair = EXAMPLES.get(label) |
|
|
if not pair: |
|
|
return "", "" |
|
|
return pair |
|
|
|
|
|
def on_run( |
|
|
schema_text: str, json_text: str, b: str, do_fix: bool |
|
|
) -> tuple[str, str, str, str]: |
|
|
|
|
|
validity_text = "" |
|
|
try: |
|
|
schema = json.loads(schema_text) |
|
|
payload = json.loads(json_text) |
|
|
rules_only = run_validation( |
|
|
schema, payload, apply_fixes=False, backend="rules-only" |
|
|
) |
|
|
hybrid = run_validation( |
|
|
schema, |
|
|
payload, |
|
|
apply_fixes=(False if b == "rules-only" else do_fix), |
|
|
backend=_backend_value(b), |
|
|
) |
|
|
r_ok = 1 if rules_only.get("valid") else 0 |
|
|
h_ok = 1 if hybrid.get("valid") else 0 |
|
|
delta = (h_ok - r_ok) * 100 |
|
|
validity_text = f"**Validity** — Rules-only: {r_ok*100}% · Hybrid: {h_ok*100}% · Δ: {delta:+d}%" |
|
|
|
|
|
rule_errors = json.dumps(hybrid.get("rule_errors", []), indent=2) |
|
|
ml_preds = json.dumps(hybrid.get("ml_predictions", []), indent=2) |
|
|
corrected = json.dumps(hybrid.get("corrected_json", payload), indent=2) |
|
|
return rule_errors, ml_preds, corrected, validity_text |
|
|
except Exception as e: |
|
|
return "[]", f"Invalid input JSON: {e}", json_text, "" |
|
|
|
|
|
load_btn.click(fn=on_load, outputs=[schema_in, json_in]) |
|
|
example_dd.change( |
|
|
fn=on_example_select, inputs=[example_dd], outputs=[schema_in, json_in] |
|
|
) |
|
|
run_btn.click( |
|
|
fn=on_run, |
|
|
inputs=[schema_in, json_in, backend, apply_minimal], |
|
|
outputs=[rule_errors_out, ml_preds_out, corrected_out, validity_md], |
|
|
) |
|
|
|
|
|
|
|
|
def on_backend_change(b: str, current: bool): |
|
|
if b == "rules-only": |
|
|
return gr.update(value=False, interactive=False) |
|
|
return gr.update(interactive=True) |
|
|
|
|
|
backend.change( |
|
|
fn=on_backend_change, inputs=[backend, apply_minimal], outputs=apply_minimal |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(share=True) |
|
|
|