diff --git a/src/jesse.erl b/src/jesse.erl index bec342fd..566e870b 100644 --- a/src/jesse.erl +++ b/src/jesse.erl @@ -31,6 +31,8 @@ , del_schema/1 , load_schemas/2 , load_schemas/3 + , materialize_refs/2 + , materialize_refs_for_schema/2 , validate/2 , validate/3 , validate_with_schema/2 @@ -117,6 +119,23 @@ load_schemas(Path, ParserFun) -> load_schemas(Path, ParserFun, ValidationFun) -> jesse_database:add_path(Path, ParserFun, ValidationFun). +%% @doc Returns corresponding schema with all references recursively resolved. +-spec materialize_refs( SchemaKey :: any() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> json_term(). +materialize_refs(SchemaKey, Options) -> + JsonSchema = jesse_database:load(SchemaKey), + materialize_refs_for_schema(JsonSchema, Options). + +%% @doc Returns the same schema, but with all references recursively resolved. +-spec materialize_refs_for_schema( Schema :: json_term() | binary() + , Options :: [{Key :: atom(), Data :: any()}] + ) -> json_term(). +materialize_refs_for_schema(Schema, Options) -> + ParserFun = proplists:get_value(parser_fun, Options, fun(X) -> X end), + ParsedSchema = try_parse(schema, ParserFun, Schema), + jesse_lib:materialize_refs(ParsedSchema, Options). + %% @doc Equivalent to {@link validate/3} where `Options' is an empty list. -spec validate( Schema :: any() , Data :: json_term() | binary() diff --git a/src/jesse_json_path.erl b/src/jesse_json_path.erl index 9f258565..66b3de8c 100644 --- a/src/jesse_json_path.erl +++ b/src/jesse_json_path.erl @@ -4,7 +4,7 @@ %% @doc Implementation of Key Value Coding style "queries" for commonly %% used Erlang data structures. -module(jesse_json_path). --export([parse/1, path/2, path/3, value/3, to_proplist/1, unwrap_value/1]). +-export([parse/1, path/2, path/3, value/3, to_proplist/1, unwrap_value/1, with_unwrapped/2]). -ifdef(erlang_deprecated_types). -type kvc_obj_node() :: proplist() | {struct, proplist()} | [{}] | dict() @@ -133,6 +133,19 @@ unwrap_value([{}]) -> []; ?IF_MAPS(unwrap_value(Map) when erlang:is_map(Map) -> maps:to_list(Map);) unwrap_value(L) -> L. +%% @doc Unwrap data, call `Fun' on unwrapped data and wrap back in the +%% same format. +-spec with_unwrapped(kvc_obj(), fun( (list()) -> list())) -> kvc_obj(). +with_unwrapped({struct, L}, Fun) -> {struct, Fun(L)}; +with_unwrapped({L}, Fun) -> {Fun(L)}; +with_unwrapped({}, _Fun) -> {}; +with_unwrapped([], _Fun) -> []; +with_unwrapped([{}], _Fun) -> [{}]; +?IF_MAPS(with_unwrapped(Map, Fun) when erlang:is_map(Map) -> maps:from_list(Fun(maps:to_list(Map)));) +with_unwrapped(L, Fun) when is_list(L) -> Fun(L); +with_unwrapped(Other, _) -> Other. + + %% Internal API -ifdef(erlang_deprecated_types). diff --git a/src/jesse_lib.erl b/src/jesse_lib.erl index 0a026c0c..2ac64a10 100644 --- a/src/jesse_lib.erl +++ b/src/jesse_lib.erl @@ -28,6 +28,7 @@ , is_array/1 , is_json_object/1 , is_null/1 + , materialize_refs/2 ]). %% Includes @@ -67,3 +68,42 @@ is_json_object(_) -> false. -spec is_null(Value :: any()) -> boolean(). is_null(null) -> true; is_null(_Value) -> false. + + +%% @doc Returns the same schema, but with all references recursively resolved +-spec materialize_refs(jesse:json_term(), [tuple()]) -> jesse:json_term(). +materialize_refs(JsonSchema, Options) -> + State = jesse_state:new(JsonSchema, Options), + jesse_json_path:with_unwrapped( + JsonSchema, + fun(Elements) -> do_materialize_refs(Elements, State) end). + +do_materialize_refs([{?REF, RefSchemaURI} | Attrs], State) -> + RefState = jesse_state:resolve_ref(State, RefSchemaURI), + RefSchema = jesse_state:get_current_schema(RefState), + RefResolvedSchema = do_materialize_refs(jesse_json_path:unwrap_value(RefSchema), RefState), + %% Recurse + NextSchema = do_materialize_refs(Attrs, State), + RefResolvedSchema ++ NextSchema; +do_materialize_refs([{Key, Value} | Attrs], State) -> + NewVal = case is_json_object(Value) of + true -> + jesse_json_path:with_unwrapped( + Value, + fun(Elements) -> + do_materialize_refs(Elements, State) + end); + false -> + case is_array(Value) of + true -> + [jesse_json_path:with_unwrapped( + Item, + fun(Elements) -> + do_materialize_refs(Elements, State) + end) + || Item <- Value]; + false -> Value + end + end, + [{Key, NewVal} | do_materialize_refs(Attrs, State)]; +do_materialize_refs(Other, _) -> Other.