Skip to content

Commit

Permalink
Merge pull request #399 from okp4/fix/empty-array
Browse files Browse the repository at this point in the history
Fix/empty array
  • Loading branch information
ccamel authored Jun 27, 2023
2 parents 4f2b668 + b530cfa commit 2efb9eb
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 21 deletions.
41 changes: 41 additions & 0 deletions x/logic/predicate/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,44 @@ 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")

// AtomEmptyArray is the term [].
var AtomEmptyArray = engine.NewAtom("[]")

// 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)
}

// 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)
}
19 changes: 11 additions & 8 deletions x/logic/predicate/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -125,11 +122,13 @@ 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 MakeEmptyArray().Compare(t, env) == 0:
return json.Marshal([]json.RawMessage{})
case MakeNull().Compare(t, env) == 0:
return json.Marshal(nil)
}

Expand All @@ -153,9 +152,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)
Expand All @@ -171,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 {
Expand Down
62 changes: 61 additions & 1 deletion x/logic/predicate/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).`,
Expand All @@ -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).`,
Expand All @@ -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').`,
Expand All @@ -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
{
Expand Down Expand Up @@ -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]).`,
Expand All @@ -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]).`,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
12 changes: 0 additions & 12 deletions x/logic/predicate/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down

0 comments on commit 2efb9eb

Please sign in to comment.