From 10022d0197c031c1d6c0ee81facc5f3a53bef611 Mon Sep 17 00:00:00 2001 From: ccamel Date: Sat, 24 Jun 2023 18:05:26 +0200 Subject: [PATCH 1/3] refactor: move json related atoms to the atom file --- x/logic/predicate/atom.go | 32 ++++++++++++++++++++++++++++++++ x/logic/predicate/json.go | 13 +++++-------- x/logic/predicate/util.go | 12 ------------ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/x/logic/predicate/atom.go b/x/logic/predicate/atom.go index e4b06a9e..88048e0e 100644 --- a/x/logic/predicate/atom.go +++ b/x/logic/predicate/atom.go @@ -7,3 +7,35 @@ import ( // AtomPair are terms with principal functor (-)/2. // For example, the term -(A, B) denotes the pair of elements A and B. var AtomPair = engine.NewAtom("-") + +// AtomJSON are terms with principal functor json/1. +// It is used to represent json objects. +var AtomJSON = engine.NewAtom("json") + +// AtomAt are terms with principal functor (@)/1. +// It is used to represent special values in json objects. +var AtomAt = engine.NewAtom("@") + +// AtomTrue is the term true. +var AtomTrue = engine.NewAtom("true") + +// AtomFalse is the term false. +var AtomFalse = engine.NewAtom("false") + +// AtomNull is the term null. +var AtomNull = engine.NewAtom("null") + +// MakeNull returns the compound term @(null). +// It is used to represent the null value in json objects. +func MakeNull() engine.Term { + return AtomAt.Apply(AtomNull) +} + +// MakeBool returns the compound term @(true) if b is true, otherwise @(false). +func MakeBool(b bool) engine.Term { + if b { + return AtomAt.Apply(AtomTrue) + } + + return AtomAt.Apply(AtomFalse) +} diff --git a/x/logic/predicate/json.go b/x/logic/predicate/json.go index aeb615d6..19e1e94a 100644 --- a/x/logic/predicate/json.go +++ b/x/logic/predicate/json.go @@ -17,9 +17,6 @@ import ( "github.com/okp4/okp4d/x/logic/util" ) -// AtomJSON is a term which represents a json as a compound term `json([Pair])`. -var AtomJSON = engine.NewAtom("json") - // JSONProlog is a predicate that will unify a JSON string into prolog terms and vice versa. // // json_prolog(?Json, ?Term) is det @@ -125,11 +122,11 @@ func termsToJSON(term engine.Term, env *engine.Env) ([]byte, error) { } switch { - case AtomBool(true).Compare(t, env) == 0: + case MakeBool(true).Compare(t, env) == 0: return json.Marshal(true) - case AtomBool(false).Compare(t, env) == 0: + case MakeBool(false).Compare(t, env) == 0: return json.Marshal(false) - case AtomNull.Compare(t, env) == 0: + case MakeNull().Compare(t, env) == 0: return json.Marshal(nil) } @@ -153,9 +150,9 @@ func jsonToTerms(value any) (engine.Term, error) { } return engine.Integer(r.Int64()), nil case bool: - return AtomBool(v), nil + return MakeBool(v), nil case nil: - return AtomNull, nil + return MakeNull(), nil case map[string]any: keys := lo.Keys(v) sort.Strings(keys) diff --git a/x/logic/predicate/util.go b/x/logic/predicate/util.go index 73a2e9dd..4718f3ee 100644 --- a/x/logic/predicate/util.go +++ b/x/logic/predicate/util.go @@ -78,18 +78,6 @@ func ListToBytes(terms engine.ListIterator, env *engine.Env) ([]byte, error) { return bt, nil } -func AtomBool(b bool) engine.Term { - var r engine.Atom - if b { - r = engine.NewAtom("true") - } else { - r = engine.NewAtom("false") - } - return engine.NewAtom("@").Apply(r) -} - -var AtomNull = engine.NewAtom("@").Apply(engine.NewAtom("null")) - // ExtractJSONTerm is an utility function that would extract all attribute of a JSON object // that is represented in prolog with the `json` atom. // From 054f85423bf4b83b39deaab7e096b615dffd491f Mon Sep 17 00:00:00 2001 From: ccamel Date: Sat, 24 Jun 2023 18:34:02 +0200 Subject: [PATCH 2/3] fix(logic): fix empty array management in json predicate --- x/logic/predicate/atom.go | 9 +++++++++ x/logic/predicate/json.go | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/x/logic/predicate/atom.go b/x/logic/predicate/atom.go index 88048e0e..14e343f0 100644 --- a/x/logic/predicate/atom.go +++ b/x/logic/predicate/atom.go @@ -22,6 +22,9 @@ var AtomTrue = engine.NewAtom("true") // AtomFalse is the term false. var AtomFalse = engine.NewAtom("false") +// AtomEmptyArray is the term []. +var AtomEmptyArray = engine.NewAtom("[]") + // AtomNull is the term null. var AtomNull = engine.NewAtom("null") @@ -39,3 +42,9 @@ func MakeBool(b bool) engine.Term { return AtomAt.Apply(AtomFalse) } + +// MakeEmptyArray returns is the compound term @([]). +// It is used to represent the empty array in json objects. +func MakeEmptyArray() engine.Term { + return AtomAt.Apply(AtomEmptyArray) +} diff --git a/x/logic/predicate/json.go b/x/logic/predicate/json.go index 19e1e94a..de00066c 100644 --- a/x/logic/predicate/json.go +++ b/x/logic/predicate/json.go @@ -126,6 +126,8 @@ func termsToJSON(term engine.Term, env *engine.Env) ([]byte, error) { return json.Marshal(true) case MakeBool(false).Compare(t, env) == 0: return json.Marshal(false) + case MakeEmptyArray().Compare(t, env) == 0: + return json.Marshal([]json.RawMessage{}) case MakeNull().Compare(t, env) == 0: return json.Marshal(nil) } @@ -168,6 +170,10 @@ func jsonToTerms(value any) (engine.Term, error) { return AtomJSON.Apply(engine.List(attributes...)), nil case []any: elements := make([]engine.Term, 0, len(v)) + if len(v) == 0 { + return MakeEmptyArray(), nil + } + for _, element := range v { term, err := jsonToTerms(element) if err != nil { From b530cfa078ee7f3ac5a6ffc275fc8181565077e2 Mon Sep 17 00:00:00 2001 From: ccamel Date: Sat, 24 Jun 2023 18:51:07 +0200 Subject: [PATCH 3/3] test(logic): improve prolog json test cases --- x/logic/predicate/json_test.go | 62 +++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/x/logic/predicate/json_test.go b/x/logic/predicate/json_test.go index c2af4dc4..dac1b61c 100644 --- a/x/logic/predicate/json_test.go +++ b/x/logic/predicate/json_test.go @@ -147,6 +147,14 @@ func TestJsonProlog(t *testing.T) { }, // ** JSON -> Prolog ** // Array + { + description: "convert empty json array into prolog", + query: `json_prolog('[]', Term).`, + wantResult: []types.TermResults{{ + "Term": "@([])", + }}, + wantSuccess: true, + }, { description: "convert json array into prolog", query: `json_prolog('["foo", "bar"]', Term).`, @@ -155,6 +163,14 @@ func TestJsonProlog(t *testing.T) { }}, wantSuccess: true, }, + { + description: "convert json array with null element into prolog", + query: `json_prolog('[null]', Term).`, + wantResult: []types.TermResults{{ + "Term": "[@(null)]", + }}, + wantSuccess: true, + }, { description: "convert json string array into prolog", query: `json_prolog('["string with space", "bar"]', Term).`, @@ -174,6 +190,22 @@ func TestJsonProlog(t *testing.T) { }}, wantSuccess: true, }, + { + description: "convert atom term to json", + query: `json_prolog(Json, foo).`, + wantResult: []types.TermResults{{ + "Json": "'\"foo\"'", + }}, + wantSuccess: true, + }, + { + description: "convert string with space to json", + query: `json_prolog(Json, 'foo bar').`, + wantResult: []types.TermResults{{ + "Json": "'\"foo bar\"'", + }}, + wantSuccess: true, + }, { description: "convert string with space to json", query: `json_prolog(Json, 'foo bar').`, @@ -182,6 +214,14 @@ func TestJsonProlog(t *testing.T) { }}, wantSuccess: true, }, + { + description: "convert empty-list atom term to json", + query: `json_prolog(Json, []).`, + wantResult: []types.TermResults{{ + "Json": "'\"[]\"'", + }}, + wantSuccess: true, + }, // ** Prolog -> JSON ** // Object { @@ -252,6 +292,14 @@ func TestJsonProlog(t *testing.T) { }, // ** Prolog -> Json ** // Array + { + description: "convert empty json array from prolog", + query: `json_prolog(Json, @([])).`, + wantResult: []types.TermResults{{ + "Json": "[]", + }}, + wantSuccess: true, + }, { description: "convert json array from prolog", query: `json_prolog(Json, [foo,bar]).`, @@ -260,6 +308,14 @@ func TestJsonProlog(t *testing.T) { }}, wantSuccess: true, }, + { + description: "convert json array with null element from prolog", + query: `json_prolog(Json, [@(null)]).`, + wantResult: []types.TermResults{{ + "Json": "'[null]'", + }}, + wantSuccess: true, + }, { description: "convert json string array from prolog", query: `json_prolog(Json, ['string with space',bar]).`, @@ -340,7 +396,6 @@ func TestJsonProlog(t *testing.T) { So(sols.Err(), ShouldBeNil) if tc.wantSuccess { - So(len(got), ShouldBeGreaterThan, 0) So(len(got), ShouldEqual, len(tc.wantResult)) for iGot, resultGot := range got { for varGot, termGot := range resultGot { @@ -404,6 +459,11 @@ func TestJsonPrologWithMoreComplexStructBidirectional(t *testing.T) { term: "json([a-b])", wantSuccess: false, }, + { + json: `'{"key1":null,"key2":[],"key3":{"nestedKey1":null,"nestedKey2":[],"nestedKey3":["a",null,null]}}'`, + term: `json([key1- @(null),key2- @([]),key3-json([nestedKey1- @(null),nestedKey2- @([]),nestedKey3-[a,@(null),@(null)]])])`, + wantSuccess: true, + }, } for nc, tc := range cases { Convey(fmt.Sprintf("#%d : given the json: %s and the term %s", nc, tc.json, tc.term), func() {