diff --git a/jschon/output.py b/jschon/output.py index 785f5dc..35507ae 100644 --- a/jschon/output.py +++ b/jschon/output.py @@ -1,7 +1,7 @@ from typing import Any, Callable, Dict, Iterable from jschon.json import JSONCompatible -from jschon.jsonschema import Result +from jschon.jsonschema import JSONSchema, Result __all__ = [ 'OutputFormatter', @@ -124,3 +124,42 @@ def visit(node: Result): return output return visit(result) + + +@output_formatter('hierarchical') +def hierarchical(result: Result) -> JSONCompatible: + def visit(node: Result): + if isinstance(node.schema_node, JSONSchema): + output = { + "valid": (valid := node.valid), + "evaluationPath": str(node.path), + "schemaLocation": str(node.absolute_uri), + "instanceLocation": str(node.instance.path), + } + nested = [] + annotations = {} + errors = {} + for child in node.children.values(): + nested += [ + childout for childout in (visit(child)) + if child.valid == valid + ] + if valid and child.annotation is not None: + annotations[child.key] = child.annotation + elif not valid and child.error is not None: + errors[child.key] = child.error + + if nested: + output["nested"] = nested + if valid and annotations: + output["annotations"] = annotations + elif not valid and errors: + output["errors"] = errors + + yield output + + else: + for child in node.children.values(): + yield from visit(child) + + return list(visit(result))[0] diff --git a/tests/test_output.py b/tests/test_output.py index 082869d..82b3125 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -3,7 +3,7 @@ import pytest from pytest import param as p -from jschon import JSON, JSONPointer, JSONSchema +from jschon import JSON, JSONPointer, JSONSchema, URI from tests import metaschema_uri_2019_09, metaschema_uri_2020_12 schema_valid = { @@ -413,3 +413,26 @@ def test_basic_output_filtering(annotations): if JSONPointer(annotation['keywordLocation'])[-1] in annotations ] assert result == filtered_output + + +@pytest.mark.parametrize('input, valid', [ + ([1, 2], True), + (['one'], True), + ([1, 'two'], False), + ([None, False], False), +]) +def test_hierarchical_output(input, valid, catalog): + schema = JSONSchema({ + "$schema": "https://json-schema.org/draft/next/schema", + "type": "array", + "anyOf": [ + {"items": {"type": "integer"}}, + {"items": {"type": "string"}}, + ] + }) + output = schema.evaluate(JSON(input)).output('hierarchical') + assert output['valid'] is valid + + output_schema = catalog.get_schema(URI('https://json-schema.org/draft/next/output/schema')) + output_validity = output_schema.evaluate(JSON(output)) + assert output_validity.valid is True