1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2007-2014, University of Amsterdam 7 VU University Amsterdam 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(json_convert, 37 [ prolog_to_json/2, % :Term, -JSON object 38 json_to_prolog/2, % +JSON, :Term 39 (json_object)/1, % +Definition 40 op(1150, fx, (json_object)) 41 ]). 42:- use_module(library(error)). 43:- use_module(library(pairs)). 44:- use_module(library(apply)). 45:- use_module(json). 46 47:- meta_predicate 48 prolog_to_json( , ), 49 json_to_prolog( , ). 50 51:- public 52 clear_cache/0, 53 prolog_list_to_json/3, % +ListIn, -ListOut, +Module 54 prolog_to_json/3, % +In, -Out, +Module 55 prolog_bool_to_json/2. % +In, -Boolean
f(Name, Type, Default, Var)
, ordered by
Name. Var is the corresponding variable in Term.138:- multifile 139 json_object_to_pairs/3, % Term, Module, Pairs 140 current_json_object/3. % Term, Module, Fields
?- json_object point(x:int, y:int, z:int=0).
The type arguments are either types as know to library(error) or
functor names of other JSON objects. The constant any
indicates an untyped argument. If this is a JSON term, it
becomes subject to json_to_prolog/2. I.e., using the type
list(any)
causes the conversion to be executed on each element
of the list.
If a field has a default, the default is used if the field is
not specified in the JSON object. Extending the record type
definition, types can be of the form (Type1|Type2). The type
null
means that the field may not be present.
Conversion of JSON to Prolog applies if all non-defaulted arguments can be found in the JSON object. If multiple rules match, the term with the highest arity gets preference.
168json_object(Declaration) :-
169 throw(error(context_error(nodirective, json_object(Declaration)), _)).
json_object_to_pairs(Term, Module, Pairs) :- <type-checks on Term>, <make Pairs from Term>.
183compile_json_objects(Spec, Clauses) :- 184 phrase(compile_objects(Spec), Clauses). 185 186compile_objects(A) --> 187 { var(A), 188 !, 189 instantiation_error(A) 190 }. 191compile_objects((A,B)) --> 192 !, 193 compile_objects(A), 194 compile_objects(B). 195compile_objects(Term) --> 196 compile_object(Term). 197 198compile_object(ObjectDef) --> 199 { prolog_load_context(module, CM), 200 strip_module(CM:ObjectDef, M, Def), 201 extra_defs(Def, Term, ExtraFields), 202 Term =.. [Constructor|Args], 203 defaults(Args, Defs, TypedArgs), 204 types(TypedArgs, Names, Types) 205 }, 206 record_to_json_clause(Constructor, M, Types, Names, ExtraFields), 207 current_clause(Constructor, M, Types, Defs, Names, ExtraFields), 208 [ (:- json_convert:clear_cache) ]. 209 210extra_defs(Term+Extra0, Term, Extra) :- 211 !, 212 must_be(list, Extra0), 213 maplist(canonical_pair, Extra0, Extra). 214extra_defs(Term, Term, []). 215 216 217canonical_pair(Var, _) :- 218 var(Var), 219 !, 220 instantiation_error(Var). 221canonical_pair(Name=Value, Name=Value) :- 222 !, 223 must_be(atom, Name). 224canonical_pair(Name-Value, Name=Value) :- 225 !, 226 must_be(atom, Name). 227canonical_pair(NameValue, Name=Value) :- 228 NameValue =.. [Name,Value], 229 !. 230canonical_pair(Pair, _) :- 231 type_error(pair, Pair).
239record_to_json_clause(Constructor, Module, Types, Names, Extra) -->
240 { type_checks(Types, VarsHead, VarsBody, Body0, Module),
241 clean_body(Body0, Body),
242 Term =.. [Constructor|VarsHead],
243 make_pairs(Names, VarsBody, Pairs, Extra),
244 Head =.. [json_object_to_pairs,Term,Module,json(Pairs)]
245 },
246 [ (json_convert:(Head :- Body)) ].
any
and Name/Arity. The latter demands a json_object term of the
given Name and Arity.
259type_checks([], [], [], true, _). 260type_checks([Type|T], [IV|IVars], [OV|OVars], (Goal, Body), M) :- 261 !, 262 type_check(Type, IV, OV, M, Goal), 263 type_checks(T, IVars, OVars, Body, M). 264 265type_check(any, IV, OV, M, prolog_to_json(IV, OV, M)) :- !. 266type_check(Name/Arity, IV, OV, M, prolog_to_json(IV, OV, M)) :- 267 !, 268 functor(IV, Name, Arity). 269type_check(boolean, IV, OV, _, prolog_bool_to_json(IV, OV)) :- !. 270type_check(list, IV, OV, M, prolog_list_to_json(IV, OV, M)) :- !. 271type_check(list(any), IV, OV, M, prolog_list_to_json(IV, OV, M)) :- !. 272type_check(list(_Type), IV, OV, M, prolog_list_to_json(IV, OV, M)) :- !. 273type_check(Type, V, V, _, Goal) :- 274 type_goal(Type, V, Goal).
true
, on
or 1
for @true and one of false
, fail
, off
or 0
for @false.
285prolog_bool_to_json(Var, _) :- 286 var(Var), 287 instantiation_error(Var). 288prolog_bool_to_json(true, @(true)). 289prolog_bool_to_json(false, @(false)). 290prolog_bool_to_json(fail, @(false)). 291prolog_bool_to_json(0, @(false)). 292prolog_bool_to_json(on, @(true)). 293prolog_bool_to_json(off, @(false)). 294prolog_bool_to_json(1, @(false)). 295prolog_bool_to_json(@(True), True) :- 296 prolog_bool_to_json(True, True).
303type_goal(Type, Var, Body) :- 304 current_type(Type, Var, Body0), 305 primitive(Body0, Body), 306 !. 307type_goal(Type, Var, is_of_type(Type, Var)). 308 309primitive((A0,B0), (A,B)) :- 310 !, 311 primitive(A0, A), 312 primitive(B0, B). 313primitive((A0;B0), (A,B)) :- 314 !, 315 primitive(A0, A), 316 primitive(B0, B). 317primitive((A0->B0), (A,B)) :- 318 !, 319 primitive(A0, A), 320 primitive(B0, B). 321primitive(_:G, G) :- 322 !, 323 predicate_property(system:G, built_in). 324primitive(G, G) :- 325 predicate_property(system:G, built_in). 326 327non_json_type(Type) :- 328 current_type(Type, _, _), 329 !.
true
statements and
perform partial evaluation on some commonly constructs that are
generated from the has_type/2 clauses in library(error).338clean_body(Var, Var) :- 339 var(Var), 340 !. 341clean_body((A0,B0), G) :- 342 !, 343 clean_body(A0, A), 344 clean_body(B0, B), 345 conj(A, B, G). 346clean_body(ground(X), true) :- % Generated from checking extra fields. 347 ground(X), 348 !. 349clean_body(memberchk(V, Values), true) :- % generated from oneof(List) 350 ground(V), ground(Values), 351 memberchk(V, Values), 352 !. 353clean_body((integer(Low) -> If ; Then), Goal) :- % generated from between(Low,High) 354 number(Low), 355 !, 356 ( integer(Low) 357 -> Goal = If 358 ; Goal = Then 359 ). 360clean_body((A->true;fail), A) :- !. % nullable fields. 361clean_body((fail->_;A), A) :- !. 362clean_body(A, A). 363 364conj(T, A, A) :- T == true, !. 365conj(A, T, A) :- T == true, !. 366conj(A, B, (A,B)). 367 368make_pairs([], [], L, L). 369make_pairs([N|TN], [V|TV], [N=V|T], Tail) :- 370 make_pairs(TN, TV, T, Tail).
376current_clause(Constructor, Module, Types, Defs, Names, Extra) --> 377 { length(Types, Arity), 378 functor(Term, Constructor, Arity), 379 extra_fields(Extra, EF), 380 Term =.. [_|Vars], 381 mk_fields(Names, Types, Defs, Vars, Fields0, EF), 382 sort(Fields0, Fields), 383 Head =.. [current_json_object, Term, Module, Fields] 384 }, 385 [ json_convert:Head ]. 386 387extra_fields([], []). 388extra_fields([Name=Value|T0], [f(Name, oneof([Value]), _, Value)|T]) :- 389 extra_fields(T0, T). 390 391mk_fields([], [], [], [], Fields, Fields). 392mk_fields([Name|TN], [Type|TT], [Def|DT], [Var|VT], 393 [f(Name, Type, Def, Var)|T], Tail) :- 394 mk_fields(TN, TT, DT, VT, T, Tail). 395 396 397/* The code below is copied from library(record) */
403defaults([], [], []). 404defaults([Arg=Default|T0], [Default|TD], [Arg|TA]) :- 405 !, 406 defaults(T0, TD, TA). 407defaults([Arg|T0], [NoDefault|TD], [Arg|TA]) :- 408 no_default(NoDefault), 409 defaults(T0, TD, TA). 410 411no_default('$no-default$').
417types([], [], []). 418types([Name:Type|T0], [Name|TN], [Type|TT]) :- 419 !, 420 must_be(atom, Name), 421 types(T0, TN, TT). 422types([Name|T0], [Name|TN], [any|TT]) :- 423 must_be(atom, Name), 424 types(T0, TN, TT). 425 426 427 /******************************* 428 * PROLOG --> JSON * 429 *******************************/
boolean
, commonly used thruth-values in Prolog are converted
to JSON booleans. Boolean translation accepts one of true
,
on
, 1
, @true, false
, fail
, off
or 0
, @false.
443prolog_to_json(Module:Term, JSON) :- 444 prolog_to_json(Term, JSON, Module). 445 446prolog_to_json(Var, _, _) :- 447 var(Var), 448 !, 449 instantiation_error(Var). 450prolog_to_json(Atomic, Atomic, _) :- 451 atomic(Atomic), 452 !. 453prolog_to_json(List, JSON, Module) :- 454 is_list(List), 455 !, 456 prolog_list_to_json(List, JSON, Module). 457prolog_to_json(Record, JSON, Module) :- 458 record_to_pairs(Record, JSON, Module), 459 !. 460prolog_to_json(Term, Term, _) :- 461 is_json_term(Term), 462 !. 463prolog_to_json(Term, _, _) :- 464 type_error(json_term, Term). 465 466record_to_pairs(T, _, _) :- 467 var(T), 468 !, 469 instantiation_error(T). 470record_to_pairs(T, JSON, M) :- 471 object_module(M, Module), 472 json_object_to_pairs(T, Module, JSON), 473 !. 474 475object_module(user, user) :- !. 476object_module(M, M). 477object_module(_, user). 478 479prolog_list_to_json([], [], _). 480prolog_list_to_json([H0|T0], [H|T], M) :- 481 prolog_to_json(H0, H, M), 482 prolog_list_to_json(T0, T, M). 483 484 485 /******************************* 486 * JSON --> PROLOG * 487 *******************************/ 488 489:- dynamic 490 json_to_prolog_rule/3, % Module, Pairs, Term 491 created_rules_for_pairs/2. % Module, Pairs 492 493clear_cache :- 494 retractall(json_to_prolog_rule(_,_,_)), 495 retractall(created_rules_for_pairs(_,_)). 496 497:- clear_cache.
If a field in a json_object is declared of type boolean
, @true
and @false are translated to true
or false
, the most
commonly used Prolog representation for truth-values.
512json_to_prolog(JSON, Module:Term) :- 513 json_to_prolog(JSON, Term, Module). 514 515json_to_prolog(json(Pairs), Term, Module) :- 516 !, 517 ( pairs_to_term(Pairs, Term, Module) 518 -> true 519 ; json_pairs_to_prolog(Pairs, Prolog, Module), 520 Term = json(Prolog) 521 ). 522json_to_prolog(List, Prolog, Module) :- 523 is_list(List), 524 !, 525 json_list_to_prolog(List, Prolog, Module). 526json_to_prolog(@(Special), @(Special), _) :- !. 527json_to_prolog(Atomic, Atomic, _). 528 529json_pairs_to_prolog([], [], _). 530json_pairs_to_prolog([Name=JSONValue|T0], [Name=PrologValue|T], Module) :- 531 json_to_prolog(JSONValue, PrologValue, Module), 532 json_pairs_to_prolog(T0, T, Module). 533 534json_list_to_prolog([], [], _). 535json_list_to_prolog([JSONValue|T0], [PrologValue|T], Module) :- 536 json_to_prolog(JSONValue, PrologValue, Module), 537 json_list_to_prolog(T0, T, Module).
548pairs_to_term(Pairs, Term, Module) :- 549 object_module(Module, M), 550 ( json_to_prolog_rule(M, Pairs, Term) 551 -> ! 552 ; created_rules_for_pairs(M, Pairs) 553 -> !, fail 554 ; pairs_args(Pairs, PairArgs), 555 sort(PairArgs, SortedPairArgs), 556 findall(Q-Rule, 557 ( create_rule(SortedPairArgs, Module, M, Term0, Body, Q), 558 Rule = (json_to_prolog_rule(M, PairArgs, Term0) :- Body) 559 ), 560 RulePairs), 561 keysort(RulePairs, ByQuality), 562 pairs_values(ByQuality, Rules), 563 maplist(asserta, Rules), 564 asserta(created_rules_for_pairs(M, PairArgs)), 565 json_to_prolog_rule(M, Pairs, Term), ! 566 ). 567 568pairs_args([], []). 569pairs_args([Name=_Value|T0], [Name=_|T]) :- 570 pairs_args(T0, T).
json_to_prolog_rule([x=X, y=Y], point(X,Y)) :- integer(X), integer(Y).
595create_rule(PairArgs, Module, M, Term, Body, Quality) :- 596 current_json_object(Term, M, Fields), 597 match_fields(PairArgs, Fields, Body0, Module, 0, Quality), 598 clean_body(Body0, Body). 599 600match_fields(Ignored, [], true, _, Q0, Q) :- 601 !, 602 length(Ignored, Len), 603 Q is Q0-2*Len. 604match_fields([Name=JSON|TP], [f(Name, Type, _, Prolog)|TF], (Goal,Body), 605 M, Q0, Q) :- 606 !, 607 match_field(Type, JSON, Prolog, M, Goal), 608 match_fields(TP, TF, Body, M, Q0, Q). 609match_fields([Name=JSON|TP], [f(OptName, Type, Def, Prolog)|TF], Body, 610 M, Q0, Q) :- 611 OptName @< Name, 612 !, 613 ( nullable(Type) 614 -> true 615 ; no_default(NoDef), 616 Def \== NoDef 617 -> Prolog = Def 618 ), 619 Q1 is Q0-1, 620 match_fields([Name=JSON|TP], TF, Body, M, Q1, Q). 621match_fields([], [f(_OptName, Type, Def, Prolog)|TF], Body, 622 M, Q0, Q) :- 623 !, 624 ( nullable(Type) 625 -> true 626 ; no_default(NoDef), 627 Def \== NoDef 628 -> Prolog = Def 629 ), 630 Q1 is Q0-1, 631 match_fields([], TF, Body, M, Q1, Q). 632match_fields([Name=_|TP], [F|TF], Body, M, Q0, Q) :- 633 arg(1, F, Next), 634 Name @< Next, 635 Q1 is Q0-2, 636 match_fields(TP, [F|TF], Body, M, Q1, Q). 637 638nullable(null). 639nullable((A|B)) :- ( nullable(A) -> true ; nullable(B) ). 640 641match_field((A|B), JSON, Prolog, M, (BodyA->true;BodyB)) :- 642 !, 643 match_field(A, JSON, Prolog, M, BodyA), 644 match_field(B, JSON, Prolog, M, BodyB). 645match_field(null, _, _, _, fail) :- !. 646match_field(any, JSON, Prolog, M, json_to_prolog(JSON,Prolog,M)) :- !. 647match_field(F/A, JSON, Prolog, M, json_to_prolog(JSON,Prolog,M)) :- 648 !, 649 functor(Prolog, F, A). 650match_field(boolean, JSON, Prolog, _, json_bool_to_prolog(JSON, Prolog)) :- !. 651match_field(list(Type), JSON, Prolog, M, json_list_to_prolog(JSON, Prolog, M)) :- 652 !, 653 ( Type = _Funcor/_Arity 654 -> true 655 ; non_json_type(Type) 656 -> true 657 ; current_json_object(Term, M, _Fields), 658 functor(Term, Type, _) 659 ). 660match_field(list, JSON, Prolog, M, Goal) :- 661 !, 662 match_field(list(any), JSON, Prolog, M, Goal). 663match_field(Type, Var, Var, _, Goal) :- 664 type_goal(Type, Var, Goal). 665 666:- public json_bool_to_prolog/2. 667 668json_bool_to_prolog(@(True), True). 669 670 671 /******************************* 672 * EXPANSION * 673 *******************************/ 674 675:- multifile 676 system:term_expansion/2. 677:- dynamic 678 system:term_expansion/2. 679 680systemterm_expansion((:- json_object(Spec)), Clauses) :- 681 compile_json_objects(Spec, Clauses)
Convert between JSON terms and Prolog application terms
The idea behind this module is to provide a flexible high-level mapping between Prolog terms as you would like to see them in your application and the standard representation of a JSON object as a Prolog term. For example, an X-Y point may be represented in JSON as
{"x":25, "y":50}
. Represented in Prolog this becomesjson([x=25,y=50])
, but this is a pretty non-natural representation from the Prolog point of view.This module allows for defining records (just like library(record)) that provide transparent two-way transformation between the two representations.
This declaration causes prolog_to_json/2 to translate the native Prolog representation into a JSON Term:
A json_object/1 declaration can define multiple objects separated by a comma (,), similar to the dynamic/1 directive. Optionally, a declaration can be qualified using a module. The conversion predicates prolog_to_json/2 and json_to_prolog/2 first try a conversion associated with the calling module. If not successful, they try conversions associated with the module
user
.JSON objects have no type. This can be solved by adding an extra field to the JSON object, e.g.
{"type":"point", "x":25, "y":50}
. As Prolog records are typed by their functor we need some notation to handle this gracefully. This is achieved by adding +Fields to the declaration. I.e.Using this declaration, the conversion becomes:
The predicate json_to_prolog/2 is often used after http_read_json/2 and prolog_to_json/2 before reply_json/1. For now we consider them separate predicates because the transformation may be too general, too slow or not needed for dedicated applications. Using a separate step also simplifies debugging this rather complicated process.
null
. Conversion to Prolog could translate @null into a variable if the desired type is notany
. Conversion to JSON could map variables tonull
, though this may be unsafe. If the Prolog term is known to be non-ground and JSON @null is a sensible mapping, we can also use this simple snipit to deal with that fact.*/