{"id":44569194,"url":"https://github.com/woolkingx/schema2object","last_synced_at":"2026-02-14T02:00:43.552Z","repository":{"id":337885394,"uuid":"1155706214","full_name":"woolkingx/schema2object","owner":"woolkingx","description":"JSON Schema as object definition - dot-access with Draft-07 validation","archived":false,"fork":false,"pushed_at":"2026-02-11T20:26:52.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-12T03:53:18.742Z","etag":null,"topics":["draft-07","json","object","python","schema","tree","validation"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/woolkingx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-11T20:13:18.000Z","updated_at":"2026-02-11T20:26:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/woolkingx/schema2object","commit_stats":null,"previous_names":["woolkingx/schema2object"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/woolkingx/schema2object","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woolkingx%2Fschema2object","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woolkingx%2Fschema2object/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woolkingx%2Fschema2object/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woolkingx%2Fschema2object/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/woolkingx","download_url":"https://codeload.github.com/woolkingx/schema2object/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/woolkingx%2Fschema2object/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29431593,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T22:20:51.549Z","status":"online","status_checked_at":"2026-02-14T02:00:07.626Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["draft-07","json","object","python","schema","tree","validation"],"created_at":"2026-02-14T02:00:21.228Z","updated_at":"2026-02-14T02:00:43.546Z","avatar_url":"https://github.com/woolkingx.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# schema2object\n\n[![Python Version](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![JSON Schema](https://img.shields.io/badge/JSON%20Schema-Draft--07-green.svg)](https://json-schema.org/specification-links.html#draft-7)\n\n\u003e **JSON Schema as object definition** — Structure maps to attributes, logic maps to methods.\n\n`schema2object` provides a Python dict wrapper with dot-access notation and automatic JSON Schema Draft-07 validation. Define your data structure once as JSON Schema, then access it naturally with type safety enforced at runtime.\n\n## Features\n\n- 🎯 **Dot-access notation** — `obj.user.name` instead of `obj['user']['name']`\n- ✅ **Draft-07 validation** — Type checking, constraints, composition keywords\n- 🔒 **Type binding** — Schema validation on every assignment\n- 📦 **Zero dependencies** — Pure Python 3.9+, stdlib only\n- 🔄 **Auto-wrapping** — Nested dicts/lists become ObjectTree instances\n- 🎨 **JSON serialization** — Built-in `ObjectTreeEncoder` support\n- 🐍 **Pythonic** — Implements `MutableMapping`, pickle, deepcopy\n\n## Installation\n\n```bash\npip install schema2object\n```\n\n## Quick Start\n\n```python\nfrom schema2object import ObjectTree\n\n# Define schema\nschema = {\n    'type': 'object',\n    'properties': {\n        'name': {'type': 'string'},\n        'age': {'type': 'integer', 'minimum': 0},\n        'email': {'type': 'string', 'pattern': r'^[^@]+@[^@]+$'}\n    },\n    'required': ['name']\n}\n\n# Create object with validation\nuser = ObjectTree({'name': 'Alice'}, schema=schema)\n\n# Dot-access\nprint(user.name)  # 'Alice'\n\n# Type checking on assignment\nuser.age = 30      # ✓ OK\nuser.age = 'thirty'  # ✗ Raises TypeError\n\n# Email validation\nuser.email = 'alice@example.com'  # ✓ OK\nuser.email = 'invalid'  # ✗ Raises TypeError (pattern mismatch)\n```\n\n## Core Concepts\n\n### Structure vs. Logic\n\n**Schema keywords → attributes** (raw access):\n```python\nschema = ObjectTree({'oneOf': [...], 'properties': {...}})\nschema.oneOf  # Get raw list\n```\n\n**Schema logic → methods** (computed access):\n```python\ndata = ObjectTree({...}, schema=schema)\nbranch = data.one_of()  # Select matching branch (XOR logic)\n```\n\n### Automatic Wrapping\n\nNested structures become ObjectTree instances automatically:\n\n```python\nobj = ObjectTree({\n    'user': {\n        'profile': {\n            'name': 'Alice'\n        }\n    }\n})\n\nobj.user.profile.name  # Full dot-access chain\n```\n\n### Default Auto-Fill\n\nMissing fields get defaults from schema:\n\n```python\nschema = {\n    'properties': {\n        'status': {'type': 'string', 'default': 'pending'},\n        'priority': {'type': 'integer', 'default': 0}\n    }\n}\n\ntask = ObjectTree({}, schema=schema)\nprint(task.status)    # 'pending'\nprint(task.priority)  # 0\n```\n\n## Schema Composition\n\n### oneOf (XOR Logic)\n\nSelect the unique matching branch:\n\n```python\nschema = {\n    'oneOf': [\n        {'properties': {'type': {'const': 'user'}, 'name': {'type': 'string'}}},\n        {'properties': {'type': {'const': 'bot'}, 'id': {'type': 'integer'}}}\n    ]\n}\n\ndata = ObjectTree({'type': 'user', 'name': 'Alice'}, schema=schema)\nbranch = data.one_of()  # Selects user branch\n\n# Type binding now works on selected branch\nbranch.name = 'Bob'  # ✓ OK\nbranch.name = 123    # ✗ TypeError\n```\n\n### anyOf (OR Logic)\n\nGet all matching branches:\n\n```python\nschema = {\n    'anyOf': [\n        {'properties': {'x': {'type': 'integer'}}},\n        {'properties': {'x': {'type': 'number'}}}\n    ]\n}\n\ndata = ObjectTree({'x': 42}, schema=schema)\nbranches = data.any_of()  # Returns list of ObjectTree instances\n```\n\n### allOf (AND Logic)\n\nMerge all sub-schemas:\n\n```python\nschema = {\n    'allOf': [\n        {'properties': {'name': {'type': 'string'}}},\n        {'properties': {'age': {'type': 'integer'}}}\n    ]\n}\n\ndata = ObjectTree({'name': 'Alice', 'age': 30}, schema=schema)\nmerged = data.all_of()  # Merged schema with both constraints\n```\n\n### Conditional Logic (if/then/else)\n\nBranch based on conditions:\n\n```python\nschema = {\n    'if': {'properties': {'role': {'const': 'admin'}}},\n    'then': {'properties': {'level': {'minimum': 5}}},\n    'else': {'properties': {'level': {'maximum': 4}}}\n}\n\nadmin = ObjectTree({'role': 'admin', 'level': 10}, schema=schema)\nresult = admin.if_then()  # Uses 'then' branch\n```\n\n### Projection (SELECT)\n\nFilter to schema-defined fields:\n\n```python\nschema = {\n    'properties': {\n        'name': {'type': 'string'},\n        'age': {'type': 'integer'}\n    }\n}\n\ndata = ObjectTree({'name': 'Alice', 'age': 30, 'extra': 'ignored'}, schema=schema)\nclean = data.project()  # {'name': 'Alice', 'age': 30}\n```\n\n## Draft-07 Validation\n\n### Type Validation\n\n```python\nschema = {'properties': {'count': {'type': 'integer'}}}\nobj = ObjectTree({}, schema=schema)\nobj.count = 42   # ✓ OK\nobj.count = 3.14 # ✗ TypeError (float is not integer)\nobj.count = True # ✗ TypeError (bool is not integer in Draft-07)\n```\n\n### Constraints\n\n**Numeric:**\n```python\n{'type': 'integer', 'minimum': 0, 'maximum': 100, 'multipleOf': 5}\n```\n\n**String:**\n```python\n{'type': 'string', 'minLength': 2, 'maxLength': 50, 'pattern': r'^[A-Z]'}\n```\n\n**Array:**\n```python\n{'type': 'array', 'minItems': 1, 'maxItems': 10, 'uniqueItems': True}\n```\n\n**Object:**\n```python\n{\n    'type': 'object',\n    'required': ['name'],\n    'minProperties': 1,\n    'maxProperties': 10,\n    'additionalProperties': False\n}\n```\n\n### Enum and Const\n\n```python\nschema = {\n    'properties': {\n        'status': {'enum': ['pending', 'active', 'done']},\n        'version': {'const': 2}\n    }\n}\n\nobj = ObjectTree({}, schema=schema)\nobj.status = 'active'   # ✓ OK\nobj.status = 'invalid'  # ✗ TypeError\nobj.version = 2         # ✓ OK\nobj.version = 3         # ✗ TypeError\n```\n\n## JSON Serialization\n\n```python\nimport json\nfrom schema2object import ObjectTree, ObjectTreeEncoder\n\nobj = ObjectTree({'user': {'name': 'Alice', 'age': 30}})\n\n# Using custom encoder\njson_str = json.dumps(obj, cls=ObjectTreeEncoder)\n\n# Or convert to dict first\njson_str = json.dumps(obj.to_dict())\n```\n\n## Python Protocols\n\n### MutableMapping\n\n```python\nobj = ObjectTree({'a': 1, 'b': 2})\n\n# Dict-like access\nobj['c'] = 3\ndel obj['a']\n'b' in obj  # True\nlen(obj)    # 2\nlist(obj.keys())    # ['b', 'c']\nlist(obj.values())  # [2, 3]\n```\n\n### Copy and Deepcopy\n\n```python\nimport copy\n\nobj = ObjectTree({'nested': {'value': 42}})\nshallow = obj.copy()\ndeep = copy.deepcopy(obj)\n```\n\n### Pickle\n\n```python\nimport pickle\n\nobj = ObjectTree({'data': [1, 2, 3]})\ndata = pickle.dumps(obj)\nrestored = pickle.loads(data)\n```\n\n### Merge (|= operator)\n\n```python\na = ObjectTree({'x': 1})\nb = ObjectTree({'y': 2})\nc = a | b  # {'x': 1, 'y': 2}\n\na |= {'z': 3}  # In-place merge\n```\n\n## Advanced Usage\n\n### Schema Propagation\n\nChild objects inherit sub-schemas:\n\n```python\nschema = {\n    'properties': {\n        'user': {\n            'type': 'object',\n            'properties': {\n                'name': {'type': 'string'}\n            }\n        }\n    }\n}\n\nobj = ObjectTree({'user': {'name': 'Alice'}}, schema=schema)\n\n# Child has sub-schema\nobj.user.name = 'Bob'  # ✓ Validated\nobj.user.name = 123    # ✗ TypeError\n```\n\n### Pattern Properties\n\n```python\nschema = {\n    'patternProperties': {\n        '^S_': {'type': 'string'},\n        '^I_': {'type': 'integer'}\n    }\n}\n\nobj = ObjectTree({}, schema=schema)\nobj['S_name'] = 'Alice'  # ✓ OK\nobj['I_count'] = 42      # ✓ OK\nobj['S_name'] = 123      # ✗ TypeError\n```\n\n### Dependencies\n\n```python\nschema = {\n    'dependencies': {\n        'credit_card': ['billing_address']\n    }\n}\n\n# If credit_card exists, billing_address is required\n```\n\n### Array Validation\n\n```python\nschema = {\n    'type': 'array',\n    'items': {'type': 'integer'},\n    'contains': {'const': 42}  # At least one item must be 42\n}\n\narr = ObjectTree([1, 42, 3], schema=schema)\narr.contains()  # True\n```\n\n## API Reference\n\n### ObjectTree\n\n**Constructor:**\n```python\nObjectTree(data=None, *, schema=None, **kwargs)\n```\n\n**Methods:**\n- `one_of()` → ObjectTree — Select unique oneOf branch\n- `any_of()` → List[ObjectTree] — Get all anyOf matches\n- `all_of()` → ObjectTree — Merge allOf schemas\n- `not_of(schema=None)` → bool — Check exclusion\n- `if_then()` → ObjectTree — Conditional branch\n- `project()` → ObjectTree — Filter to schema fields\n- `contains(schema=None)` → bool — Array element check\n- `to_dict()` → dict — Unwrap to native Python\n\n**Properties:**\n- `is_mapping` → bool\n- `is_sequence` → bool\n\n### ObjectTreeEncoder\n\nJSON encoder for ObjectTree instances:\n\n```python\njson.dumps(obj, cls=ObjectTreeEncoder)\n```\n\n## Common Pitfalls\n\n### 1. Use Mapping, Not dict\n\nObjectTree is NOT a dict subclass:\n\n```python\n# ✗ Wrong\nif isinstance(data, dict):\n    ...\n\n# ✓ Correct\nfrom collections.abc import Mapping\nif isinstance(data, Mapping):\n    ...\n```\n\n### 2. Call Methods Before Type Binding\n\nSchema composition requires method call first:\n\n```python\n# ✗ Wrong\nobj.field = value  # Validates against original schema\n\n# ✓ Correct\nbranch = obj.one_of()\nbranch.field = value  # Validates against selected branch\n```\n\n### 3. Circular Imports\n\nUse TYPE_CHECKING for type hints:\n\n```python\nfrom typing import TYPE_CHECKING\nif TYPE_CHECKING:\n    from schema2object import ObjectTree\n```\n\n## Development\n\n```bash\n# Clone repository\ngit clone https://github.com/TODO/schema2object.git\ncd schema2object\n\n# Run tests\npython3 -m pytest tests/test_objecttree.py -v\n\n# Run specific test class\npython3 -m pytest tests/test_objecttree.py::TestTypeBinding -v\n```\n\n**Note:** Tests must be run from parent directory due to relative imports.\n\n## Requirements\n\n- Python 3.9+\n- No external dependencies\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions welcome! Please open an issue or submit a pull request.\n\n## Acknowledgments\n\nBuilt for the [mcp-mindnext-psm](https://github.com/TODO/mcp-mindnext-psm) project as a lightweight, dependency-free alternative to heavy JSON Schema validators.\n\n## Related Projects\n\n- [jsonschema](https://github.com/python-jsonschema/jsonschema) — Full-featured JSON Schema validator\n- [pydantic](https://github.com/pydantic/pydantic) — Data validation using Python type annotations\n- [marshmallow](https://github.com/marshmallow-code/marshmallow) — Object serialization/deserialization\n\n---\n\n**Why schema2object?**\n\nUnlike other solutions, `schema2object` provides:\n- **Dot-access** instead of bracket notation\n- **Zero dependencies** for minimal footprint\n- **Schema as object** for uniform access patterns\n- **Method-based logic** separating structure from behavior\n- **Draft-07 compliance** with full composition support\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoolkingx%2Fschema2object","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwoolkingx%2Fschema2object","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwoolkingx%2Fschema2object/lists"}