1010
1111from ._compat import Self , TypeAlias
1212from .json_schema .types import AnyType , JSONSchemaType
13- from .typing import JSON , JSONLogicPrimitive , OperatorArgument
13+ from .typing import JSON , JSONLogicPrimitive , JSONObject , OperatorArgument
1414
1515if TYPE_CHECKING :
1616 # This is a hack to make Pylance think `TypeAlias` comes from `typing`
@@ -62,27 +62,35 @@ def __init__(self, message: str, /) -> None:
6262 self .message = message
6363
6464
65- NormalizedExpression : TypeAlias = "dict[str, list[JSONLogicExpression]]"
65+ ExprArgument : TypeAlias = "JSONLogicPrimitive | JSONLogicExpression | list[ExprArgument]"
66+
67+ NormalizedExpression : TypeAlias = "dict[str, list[ExprArgument]]"
6668
6769
6870@dataclass
6971class JSONLogicExpression :
7072 """A parsed and normalized JSON Logic expression.
7173
72- A JSON Logic expression can be:
73-
74- - a single item dictionary, mapping the operator key to another :class:`JSONLogicExpression`,
75- - a :data:`~jsonlogic.typing.JSONLogicPrimitive`.
74+ The underlying structure of an expression is a single item dictionary,
75+ mapping the operator key to a list of arguments.
7676
7777 All JSON Logic expressions should be instantiated using the :meth:`from_json` constructor::
7878
79- expr = JSONLogicExpression.from_json(...)
79+ expr = JSONLogicExpression.from_json({"op": ...} )
8080 """
8181
82- expression : JSONLogicPrimitive | NormalizedExpression
82+ expression : NormalizedExpression
83+
84+ @classmethod
85+ def _parse_impl (cls , json : JSON ) -> ExprArgument :
86+ if isinstance (json , dict ):
87+ return cls .from_json (json )
88+ if isinstance (json , list ):
89+ return [cls ._parse_impl (s ) for s in json ]
90+ return json
8391
8492 @classmethod
85- def from_json (cls , json : JSON ) -> Self : # TODO disallow list? TODO fix type errors
93+ def from_json (cls , json : JSONObject ) -> Self :
8694 """Build a JSON Logic expression from JSON data.
8795
8896 Operator arguments are recursively normalized to a :class:`list`::
@@ -91,30 +99,34 @@ def from_json(cls, json: JSON) -> Self: # TODO disallow list? TODO fix type err
9199 assert expr.expression == {"var": ["varname"]}
92100 """
93101 if not isinstance (json , dict ):
94- return cls ( expression = json ) # type: ignore
102+ raise ValueError ( "The root node of the expression must be a dict" )
95103
96104 operator , op_args = next (iter (json .items ()))
97105 if not isinstance (op_args , list ):
98106 op_args = [op_args ]
99107
100- sub_expressions = [cls .from_json ( op_arg ) for op_arg in op_args ]
108+ return cls ({ operator : [cls ._parse_impl ( arg ) for arg in op_args ]})
101109
102- return cls ({operator : sub_expressions }) # type: ignore
110+ def _as_op_impl (self , op_arg : ExprArgument , operator_registry : OperatorRegistry ) -> OperatorArgument :
111+ if isinstance (op_arg , JSONLogicExpression ):
112+ return op_arg .as_operator_tree (operator_registry )
113+ if isinstance (op_arg , list ):
114+ return [self ._as_op_impl (sub_arg , operator_registry ) for sub_arg in op_arg ]
115+ return op_arg
103116
104- def as_operator_tree (self , operator_registry : OperatorRegistry ) -> JSONLogicPrimitive | Operator :
117+ def as_operator_tree (self , operator_registry : OperatorRegistry ) -> Operator :
105118 """Return a recursive tree of operators, using the provided registry as a reference.
106119
107120 Args:
108121 operator_registry: The registry to use to resolve operator IDs.
109122
110123 Returns:
111- The current expression if it is a :data:`~jsonlogic.typing.JSONLogicPrimitive`
112- or an :class:`Operator` instance.
124+ An :class:`Operator` instance.
113125 """
114126 if not isinstance (self .expression , dict ):
115127 return self .expression
116128
117129 op_id , op_args = next (iter (self .expression .items ()))
118130 OperatorCls = operator_registry .get (op_id )
119131
120- return OperatorCls .from_expression (op_id , [op_arg . as_operator_tree ( operator_registry ) for op_arg in op_args ])
132+ return OperatorCls .from_expression (op_id , [self . _as_op_impl ( op_arg , operator_registry ) for op_arg in op_args ])
0 commit comments