From 3aaca255df34f42fb1995ea0ad831b903fd93530 Mon Sep 17 00:00:00 2001 From: Peng Ding Date: Wed, 23 Sep 2026 18:48:29 -0500 Subject: [PATCH 1/2] fix(jsonschema): preserve multi-branch anyOf/oneOf unions _simplify_node() previously collapsed all anyOf/oneOf to the first non-null branch, which was correct for simple nullable patterns (T | None) but lossy for genuine unions like float | list[float] | None. Now only collapses when there is exactly one non-null branch. When there are 2+ non-null branches, they are preserved as anyOf with the null branch stripped. Ref: Oaklight/ToolRegistry#269 --- jsonschema/jsonschema.py | 9 +++--- jsonschema/test_jsonschema_correctness.py | 39 ++++++++++++++++++++--- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/jsonschema/jsonschema.py b/jsonschema/jsonschema.py index 58d963b..77b7a37 100644 --- a/jsonschema/jsonschema.py +++ b/jsonschema/jsonschema.py @@ -1,5 +1,5 @@ # /// zerodep -# version = "0.4.0" +# version = "0.4.1" # deps = [] # tier = "subsystem" # category = "validation" @@ -407,8 +407,7 @@ def _simplify_node(schema: dict[str, Any]) -> dict[str, Any]: if len(non_null) == 1: base = _deep_merge_two(base, non_null[0]) elif len(non_null) > 1: - # Lossy but safe for LLM tool schemas: keep first non-null variant. - base = _deep_merge_two(base, non_null[0]) + base["anyOf"] = non_null # else: all null — base stays as-is if has_null: @@ -451,8 +450,8 @@ def simplify_unions(schema: dict[str, Any]) -> dict[str, Any]: - Nullable pattern ``[{type: T}, {type: null}]`` → ``{type: T, nullable: true}`` - Single-variant: unwrap. - - Multi-variant: keep first non-null variant (lossy but safe for LLM tool - schemas). + - Multi-variant with null: strip the ``{type: null}`` branch, keep remaining + branches as ``anyOf``. Args: schema: A JSON Schema dict. diff --git a/jsonschema/test_jsonschema_correctness.py b/jsonschema/test_jsonschema_correctness.py index bacfba5..0a19f71 100644 --- a/jsonschema/test_jsonschema_correctness.py +++ b/jsonschema/test_jsonschema_correctness.py @@ -574,7 +574,7 @@ def test_single_variant_no_null(self): assert "nullable" not in result assert "anyOf" not in result - def test_multi_variant_keeps_first(self): + def test_multi_variant_preserved(self): schema = { "anyOf": [ {"type": "string"}, @@ -582,8 +582,8 @@ def test_multi_variant_keeps_first(self): ] } result = simplify_unions(schema) - assert result["type"] == "string" - assert "anyOf" not in result + assert result["anyOf"] == [{"type": "string"}, {"type": "integer"}] + assert "nullable" not in result def test_multi_variant_with_null(self): schema = { @@ -594,9 +594,40 @@ def test_multi_variant_with_null(self): ] } result = simplify_unions(schema) - assert result["type"] == "string" + assert result["anyOf"] == [{"type": "string"}, {"type": "integer"}] + assert result["nullable"] is True + + def test_multi_variant_number_and_array(self): + schema = { + "anyOf": [ + {"type": "number"}, + {"type": "array", "items": {"type": "number"}}, + {"type": "null"}, + ] + } + result = simplify_unions(schema) + assert result["anyOf"] == [ + {"type": "number"}, + {"type": "array", "items": {"type": "number"}}, + ] assert result["nullable"] is True + def test_multi_variant_no_null(self): + schema = { + "oneOf": [ + {"type": "string"}, + {"type": "number"}, + {"type": "boolean"}, + ] + } + result = simplify_unions(schema) + assert result["anyOf"] == [ + {"type": "string"}, + {"type": "number"}, + {"type": "boolean"}, + ] + assert "nullable" not in result + def test_all_null_variants(self): schema = {"anyOf": [{"type": "null"}, {"type": "null"}]} result = simplify_unions(schema) From ef8fc4fce6e83f35fa98b0b2dd8a348cc5aba57b Mon Sep 17 00:00:00 2001 From: Peng Ding Date: Wed, 23 Sep 2026 19:15:39 -0500 Subject: [PATCH 2/2] fix(jsonschema): preserve original anyOf/oneOf keyword in multi-branch unions Address review nit from Clementine and Elena: use `base[keyword]` instead of hardcoded `base["anyOf"]` so oneOf inputs retain their keyword. --- jsonschema/jsonschema.py | 4 ++-- jsonschema/test_jsonschema_correctness.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jsonschema/jsonschema.py b/jsonschema/jsonschema.py index 77b7a37..4164074 100644 --- a/jsonschema/jsonschema.py +++ b/jsonschema/jsonschema.py @@ -407,7 +407,7 @@ def _simplify_node(schema: dict[str, Any]) -> dict[str, Any]: if len(non_null) == 1: base = _deep_merge_two(base, non_null[0]) elif len(non_null) > 1: - base["anyOf"] = non_null + base[keyword] = non_null # else: all null — base stays as-is if has_null: @@ -451,7 +451,7 @@ def simplify_unions(schema: dict[str, Any]) -> dict[str, Any]: - Nullable pattern ``[{type: T}, {type: null}]`` → ``{type: T, nullable: true}`` - Single-variant: unwrap. - Multi-variant with null: strip the ``{type: null}`` branch, keep remaining - branches as ``anyOf``. + branches under the original keyword (``anyOf`` or ``oneOf``). Args: schema: A JSON Schema dict. diff --git a/jsonschema/test_jsonschema_correctness.py b/jsonschema/test_jsonschema_correctness.py index 0a19f71..bb0a2e3 100644 --- a/jsonschema/test_jsonschema_correctness.py +++ b/jsonschema/test_jsonschema_correctness.py @@ -621,7 +621,7 @@ def test_multi_variant_no_null(self): ] } result = simplify_unions(schema) - assert result["anyOf"] == [ + assert result["oneOf"] == [ {"type": "string"}, {"type": "number"}, {"type": "boolean"},