{"id":"json-validation-and-transform","version":"1.0.0","primitive":"code_execution","description":"Validate and transform JSON data against a schema using pydantic","registry_refs":["pydantic"],"tags":["pydantic","json","validation","schema","transform","serialization","data-modeling"],"solves":["extra fields silently accepted","deprecated parse_obj usage","missing field-level error messages"],"auth_required":false,"verified":true,"last_verified":"2026-04-13","next_check":"2026-07-13","eval_result":"null","eval_env":"null","mast":[],"ref":"https://arxiv.org/abs/2503.13657","executable":"# ============================================\n# checklist:     json-validation-and-transform\n# version:       1.0.0\n# primitive:     code_execution\n# description:   Validate and transform JSON data against a schema using pydantic\n# registry_refs: pydantic\n# auth_required: false\n# verified:      true\n# last_verified: 2026-04-13\n# next_check:    2026-07-13\n# eval_result:   null\n# eval_env:      null\n#\n# MAST FAILURE MODES ADDRESSED:\n# FM-1.1 Disobey Task Specification    — strict pydantic model rejects extra fields\n# FM-2.2 Fail to Ask for Clarification — hard registry URL, no guessing import paths\n# FM-2.6 Reasoning-Action Mismatch     — model_validate() not deprecated parse_obj()\n# FM-3.2 No or Incomplete Verification — invalid input tested, not just valid input\n# FM-3.3 Incorrect Verification        — field-level assertions, not just type check\n#\n# tags:   pydantic, json, validation, schema, transform, serialization, data-modeling\n# solves: extra fields silently accepted, deprecated parse_obj usage, missing field-level error messages\n# ref: https://arxiv.org/abs/2503.13657\n#\n# INPUTS:\n#   VALID_JSON — string, JSON to parse and validate (default: '{\"name\":\"hello world\",\"age\":30,\"email\":\"hello@world.com\",\"score\":9.5}')\n#\n# OUTPUTS:\n#   valid_input_parsed       — bool, valid JSON parsed and all fields matched\n#   json_round_trip_verified — bool, serialized back to JSON and fields intact\n#   invalid_age_rejected     — bool, negative age raises ValidationError\n#   invalid_email_rejected   — bool, bad email raises ValidationError\n#   extra_fields_rejected    — bool, unknown field raises ValidationError\n#   missing_field_rejected   — bool, missing required field raises ValidationError\n# ============================================\n\nimport sys\nimport json\nimport subprocess\nimport requests\n\n# ----------------------------------------\n# PRE_EXECUTION\n# FM-2.2: hard URL — fetch pydantic registry\n# ----------------------------------------\n\nMAX_RETRIES = 2\nregistry = None\n\nfor attempt in range(MAX_RETRIES):\n    try:\n        r = requests.get(\n            \"https://checklist.day/api/registry/pydantic\",\n            timeout=10\n        )\n        if r.status_code == 200:\n            registry = r.json()\n            break\n    except requests.exceptions.RequestException:\n        pass\n\nassert registry is not None, \\\n    \"ABORT: pydantic registry fetch failed after 2 attempts\"\n\nassert registry.get(\"imports\"), \\\n    \"ABORT: imports missing from pydantic registry\"\nassert registry.get(\"install\"), \\\n    \"ABORT: install missing from pydantic registry\"\n\n# FM-2.4: surface breaking warnings\nbreaking = [w for w in registry.get(\"warnings\", []) if w.get(\"severity\") == \"breaking\"]\nif breaking:\n    print(f\"PRE_EXECUTION: {len(breaking)} breaking warning(s):\")\n    for w in breaking:\n        print(f\"  [!] [{w.get('affected_versions', 'all')}] {w['message'][:120]}\")\n        print(f\"      fix: {w['fix'][:100]}\")\n\nprint()\nprint(\"PRE_EXECUTION: registry verified ✓\")\nprint(f\"  install : {registry['install'][0]['cmd']}\")\n\n# ----------------------------------------\n# EXECUTION\n# FM-2.6: model_validate() — not deprecated parse_obj()\n# philosophy: get it done — auto-install\n# ----------------------------------------\n\ntry:\n    from pydantic import BaseModel, ValidationError, field_validator\n    from typing import Optional\nexcept ImportError:\n    pkg = registry['install'][0]['cmd'].replace(\"pip install \", \"\").strip()\n    print(f\"\\nEXECUTION: pydantic not found — installing {pkg}...\")\n    subprocess.check_call(\n        [sys.executable, \"-m\", \"pip\", \"install\", pkg],\n        stdout=subprocess.DEVNULL,\n        stderr=subprocess.DEVNULL\n    )\n    print(f\"EXECUTION: {pkg} installed ✓\")\n    from pydantic import BaseModel, ValidationError, field_validator\n    from typing import Optional\n\n# define schema\nclass UserRecord(BaseModel):\n    model_config = {\"extra\": \"forbid\"}  # FM-1.1: reject unknown fields\n\n    name: str\n    age: int\n    email: str\n    score: Optional[float] = None\n\n    @field_validator(\"age\")\n    @classmethod\n    def age_must_be_positive(cls, v):\n        if v < 0:\n            raise ValueError(\"age must be positive\")\n        return v\n\n    @field_validator(\"email\")\n    @classmethod\n    def email_must_have_at(cls, v):\n        if \"@\" not in v:\n            raise ValueError(\"invalid email\")\n        return v\n\n# 1. valid input\nvalid_json = '{\"name\": \"hello world\", \"age\": 30, \"email\": \"hello@world.com\", \"score\": 9.5}'\nrecord = UserRecord.model_validate(json.loads(valid_json))  # FM-2.6: not parse_obj()\n\nassert record.name == \"hello world\", \\\n    f\"FAIL: expected name 'hello world', got '{record.name}'\"\nassert record.age == 30, \\\n    f\"FAIL: expected age 30, got {record.age}\"\nassert record.email == \"hello@world.com\", \\\n    f\"FAIL: expected correct email, got '{record.email}'\"\nassert record.score == 9.5, \\\n    f\"FAIL: expected score 9.5, got {record.score}\"\n\nprint(\"EXECUTION: valid input parsed correctly ✓\")\n\n# 2. transform — serialize back to JSON\noutput_json = record.model_dump_json()\noutput_dict = json.loads(output_json)\n\nassert output_dict[\"name\"] == \"hello world\", \\\n    \"FAIL: round-trip serialization failed\"\n\nprint(\"EXECUTION: JSON round-trip verified ✓\")\n\n# ----------------------------------------\n# POST_EXECUTION\n# FM-3.2: test invalid inputs — incomplete verification\n#         if we only test valid input we miss half the contract\n# FM-3.3: assert specific error messages, not just \"error occurred\"\n# ----------------------------------------\n\n# invalid age\ntry:\n    UserRecord.model_validate({\"name\": \"test\", \"age\": -1, \"email\": \"a@b.com\"})\n    assert False, \"FAIL: negative age should have raised ValidationError\"\nexcept ValidationError as e:\n    assert \"age must be positive\" in str(e), \\\n        f\"FAIL: wrong error for negative age: {e}\"\n\nprint(\"POST_EXECUTION: negative age rejected correctly ✓\")\n\n# invalid email\ntry:\n    UserRecord.model_validate({\"name\": \"test\", \"age\": 25, \"email\": \"notanemail\"})\n    assert False, \"FAIL: invalid email should have raised ValidationError\"\nexcept ValidationError as e:\n    assert \"invalid email\" in str(e), \\\n        f\"FAIL: wrong error for invalid email: {e}\"\n\nprint(\"POST_EXECUTION: invalid email rejected correctly ✓\")\n\n# extra fields rejected (FM-1.1)\ntry:\n    UserRecord.model_validate({\"name\": \"test\", \"age\": 25, \"email\": \"a@b.com\", \"unknown_field\": \"x\"})\n    assert False, \"FAIL: extra field should have raised ValidationError\"\nexcept ValidationError as e:\n    assert \"extra\" in str(e).lower() or \"forbidden\" in str(e).lower(), \\\n        f\"FAIL: wrong error for extra field: {e}\"\n\nprint(\"POST_EXECUTION: extra fields rejected correctly ✓\")\n\n# missing required field\ntry:\n    UserRecord.model_validate({\"name\": \"test\", \"age\": 25})\n    assert False, \"FAIL: missing email should have raised ValidationError\"\nexcept ValidationError as e:\n    assert \"email\" in str(e), \\\n        f\"FAIL: wrong error for missing field: {e}\"\n\nprint(\"POST_EXECUTION: missing required field rejected correctly ✓\")\n\nresult = {\n    \"status\": \"pass\",\n    \"valid_input_parsed\": True,\n    \"json_round_trip_verified\": True,\n    \"invalid_age_rejected\": True,\n    \"invalid_email_rejected\": True,\n    \"extra_fields_rejected\": True,\n    \"missing_field_rejected\": True,\n}\nprint(result)\nprint(\"PASS\")\n"}