Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions jsonsubschema/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
'''
"""
Created on August 6, 2019
@author: Andrew Habib
'''

"""

from jsonsubschema import api
from jsonsubschema import config
from jsonsubschema import exceptions
from jsonsubschema import _canonicalization
from jsonsubschema import _explain

isSubschema = api.isSubschema
meetSchemas = api.meet
joinSchemas = api.join
isEquivalent = api.isEquivalent
schemaDiff = api.schemaDiff

canonicalizeSchema = _canonicalization.canonicalize_schema

set_debug = config.set_debug
set_warn_uninhabited = config.set_warn_uninhabited

is_subschema = api.isSubschema
is_subschema_with_reason = api.is_subschema_with_reason
SubschemaResult = _explain.SubschemaResult
84 changes: 81 additions & 3 deletions jsonsubschema/_canonicalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ def canonicalize_dict(d, outer_key=None):
# Start canonicalization. Don't modify original dict.
d = copy.deepcopy(d)

# Rewrite if/then/else into anyOf+allOf before processing connectors.
if "if" in d:
d = canonicalize_if_then_else(d)
has_connectors = definitions.Jconnectors.intersection(d.keys())

if has_connectors:
return canonicalize_connectors(d)
elif "enum" in d.keys():
Expand Down Expand Up @@ -178,6 +183,32 @@ def canonicalize_const(d):
d["enum"] = [d.pop("const")]
return canonicalize_enum(d)


def canonicalize_if_then_else(d):
"""Rewrite if/then/else into equivalent anyOf+allOf.

{if: C, then: T, else: E} becomes:
{anyOf: [{allOf: [C, T]}, {allOf: [{not: C}, E]}]}

Combined with any other keywords in d via allOf.
"""
if_schema = d.pop("if")
then_schema = d.pop("then", {}) # defaults to {} (accept everything)
else_schema = d.pop("else", {}) # defaults to {} (accept everything)

rewritten = {"anyOf": [
{"allOf": [if_schema, then_schema]},
{"allOf": [{"not": if_schema}, else_schema]},
]}

# If d still has other keywords, combine via allOf
remaining = {k: v for k, v in d.items()
if k in definitions.Jkeywords}
if remaining:
return {"allOf": [remaining, rewritten]}
else:
return rewritten

def canonicalize_connectors(d):
connectors = definitions.Jconnectors.intersection(d.keys())
lhs_kw = definitions.Jkeywords.intersection(d.keys())
Expand Down Expand Up @@ -310,9 +341,53 @@ def rewrite_enum(d):
return ret
# return canonicalize_dict(ret)

# Unsupported cases of rewriting enums
elif t == 'array' or t == 'object':
raise UnsupportedEnumCanonicalization(tau=t, schema=d)
elif t == 'array':
ret = {"anyOf": []}
for arr_val in enum:
if not isinstance(arr_val, list):
continue
schema_for_val = {
"type": "array",
"minItems": len(arr_val),
"maxItems": len(arr_val),
}
if len(arr_val) > 0:
schema_for_val["items"] = [
canonicalize_dict({"enum": [v]}) for v in arr_val
]
schema_for_val["additionalItems"] = False
ret["anyOf"].append(schema_for_val)
if ret["anyOf"]:
ret["enum"] = enum
return ret
else:
return BOT

elif t == 'object':
ret = {"anyOf": []}
for obj_val in enum:
if not isinstance(obj_val, dict):
continue
schema_for_val = {
"type": "object",
"additionalProperties": False,
}
if obj_val:
schema_for_val["properties"] = {
k: canonicalize_dict({"enum": [v]})
for k, v in obj_val.items()
}
schema_for_val["required"] = sorted(obj_val.keys())
else:
schema_for_val["properties"] = {}
schema_for_val["minProperties"] = len(obj_val)
schema_for_val["maxProperties"] = len(obj_val)
ret["anyOf"].append(schema_for_val)
if ret["anyOf"]:
ret["enum"] = enum
return ret
else:
return BOT


def simplify_schema_and_embed_checkers(s):
Expand Down Expand Up @@ -341,6 +416,9 @@ def simplify_schema_and_embed_checkers(s):
s["additionalItems"] = simplify_schema_and_embed_checkers(
s["additionalItems"])

if "contains" in s and utils.is_dict(s["contains"]):
s["contains"] = simplify_schema_and_embed_checkers(s["contains"])

# json.object specific
if "properties" in s:
s["properties"] = dict([(k, simplify_schema_and_embed_checkers(v))
Expand Down
Loading