Skip to content

Commit

Permalink
Json objects to document func (#19)
Browse files Browse the repository at this point in the history
* Added new function ObjectsToDocument

* lint & fixes

* Updated to remove type conversion as already converted in the JSONata

* add some errors

---------

Co-authored-by: James Weeks <[email protected]>
Co-authored-by: tbal999 <[email protected]>
  • Loading branch information
3 people authored Oct 2, 2023
1 parent 44fd010 commit bbead99
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 34 deletions.
35 changes: 18 additions & 17 deletions callable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package jsonata

import (
"errors"
"github.com/stretchr/testify/assert"
"math"
"reflect"
"regexp"
Expand All @@ -15,6 +14,8 @@ import (
"sync"
"testing"

"github.com/stretchr/testify/assert"

"github.com/xiatechs/jsonata-go/jparse"
"github.com/xiatechs/jsonata-go/jtypes"
)
Expand Down Expand Up @@ -2381,19 +2382,19 @@ func TestRegexCallable(t *testing.T) {
end: 5,
groups: []string{},
next: &matchCallable{
callableName: callableName{
sync.Mutex{},
"next",
},
callableName: callableName{
sync.Mutex{},
"next",
},
match: "ad",
start: 5,
end: 7,
groups: []string{},
next: &matchCallable{
callableName: callableName{
sync.Mutex{},
"next",
},
callableName: callableName{
sync.Mutex{},
"next",
},
match: "ab",
start: 7,
end: 9,
Expand Down Expand Up @@ -2452,10 +2453,10 @@ func TestRegexCallable(t *testing.T) {
"d",
},
next: &matchCallable{
callableName: callableName{
sync.Mutex{},
"next",
},
callableName: callableName{
sync.Mutex{},
"next",
},
match: "ab",
start: 7,
end: 9,
Expand Down Expand Up @@ -2524,10 +2525,10 @@ func TestRegexCallable(t *testing.T) {
"", // undefined in jsonata-js
},
next: &matchCallable{
callableName: callableName{
sync.Mutex{},
"next",
},
callableName: callableName{
sync.Mutex{},
"next",
},
match: "ab",
start: 7,
end: 9,
Expand Down
6 changes: 6 additions & 0 deletions env.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ var baseEnv = initBaseEnv(map[string]Extension{
EvalContextHandler: defaultContextHandler,
},

"objectsToDocument": {
Func: jlib.ObjectsToDocument,
UndefinedHandler: defaultUndefinedHandler,
EvalContextHandler: nil,
},

/*
EXTENDED END
*/
Expand Down
3 changes: 2 additions & 1 deletion errrors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package jsonata

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
)

func TestErrors(t *testing.T) {
Expand Down
3 changes: 2 additions & 1 deletion eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
package jsonata

import (
"github.com/stretchr/testify/assert"
"math"
"reflect"
"regexp"
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/xiatechs/jsonata-go/jlib"
"github.com/xiatechs/jsonata-go/jparse"
"github.com/xiatechs/jsonata-go/jtypes"
Expand Down
53 changes: 53 additions & 0 deletions jlib/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jlib

import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
Expand Down Expand Up @@ -229,3 +230,55 @@ func ObjMerge(i1, i2 interface{}) interface{} {

return output
}

// setValue sets the value in the obj map at the specified dot notation path.
func setValue(obj map[string]interface{}, path string, value interface{}) {
paths := strings.Split(path, ".") // Split the path into parts

// Iterate through path parts to navigate/create nested maps
for i := 0; i < len(paths)-1; i++ {
// If the key does not exist, create a new map at the key
_, ok := obj[paths[i]]
if !ok {
obj[paths[i]] = make(map[string]interface{})
}
// Move to the next nested map
obj, ok = obj[paths[i]].(map[string]interface{})
if !ok {
continue
}
}

obj[paths[len(paths)-1]] = value
}

// objectsToDocument converts an array of Items to a nested map according to the Code paths.
func ObjectsToDocument(input interface{}) (interface{}, error) {
trueInput, ok := input.([]interface{})
if !ok {
return nil, errors.New("$objectsToDocument input must be an array of objects")
}

output := make(map[string]interface{}) // Initialize the output map
// Iterate through each item in the input
for _, itemToInterface := range trueInput {
item, ok := itemToInterface.(map[string]interface{})
if !ok {
return nil, errors.New("$objectsToDocument input must be an array of objects with Code and Value fields")
}
// Call setValue for each item to set the value in the output map
code, ok := item["Code"].(string)
if !ok {
return nil, errors.New("$objectsToDocument input must be an array of objects with Code and Value fields")
}

value, ok := item["Value"]
if !ok {
return nil, errors.New("$objectsToDocument input must be an array of objects with Code and Value fields")
}

setValue(output, code, value)
}

return output, nil // Return the output map
}
2 changes: 1 addition & 1 deletion jlib/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func Random() float64 {
// It does this by converting back and forth to strings to
// avoid floating point rounding errors, e.g.
//
// 4.525 * math.Pow10(2) returns 452.50000000000006
// 4.525 * math.Pow10(2) returns 452.50000000000006
func multByPow10(x float64, n int) float64 {
if n == 0 || math.IsNaN(x) || math.IsInf(x, 0) {
return x
Expand Down
6 changes: 3 additions & 3 deletions jlib/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,9 @@ func Join(values reflect.Value, separator jtypes.OptionalString) (string, error)
// regular expression in the source string. Each object in the
// array has the following fields:
//
// match - the substring matched by the regex
// index - the starting offset of this match
// groups - any captured groups for this match
// match - the substring matched by the regex
// index - the starting offset of this match
// groups - any captured groups for this match
//
// The optional third argument specifies the maximum number
// of matches to return. By default, Match returns all matches.
Expand Down
2 changes: 1 addition & 1 deletion jparse/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// syntax trees. Most clients will not need to work with
// this package directly.
//
// Usage
// # Usage
//
// Call the Parse function, passing a JSONata expression as
// a string. If an error occurs, it will be of type Error.
Expand Down
3 changes: 2 additions & 1 deletion jparse/jparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ package jparse_test

import (
"fmt"
"github.com/stretchr/testify/assert"
"regexp"
"regexp/syntax"
"strings"
"testing"
"unicode/utf8"

"github.com/stretchr/testify/assert"

"github.com/xiatechs/jsonata-go/jparse"
)

Expand Down
3 changes: 2 additions & 1 deletion jparse/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ package jparse

import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
)

type lexerTestCase struct {
Expand Down
13 changes: 7 additions & 6 deletions jsonata-test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,13 @@ func runTest(tc testCase, dataDir string, path string) (bool, error) {
// loadTestExprFile loads a jsonata expression from a file and returns the
// expression
// For example, one test looks like this
// {
// "expr-file": "case000.jsonata",
// "dataset": null,
// "bindings": {},
// "result": 2
// }
//
// {
// "expr-file": "case000.jsonata",
// "dataset": null,
// "bindings": {},
// "result": 2
// }
//
// We want to load the expression from case000.jsonata so we can use it
// as an expression in the test case
Expand Down
1 change: 0 additions & 1 deletion jsonata.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ type evaluator interface {
}

type simple struct {

}

func (s simple) InitialEval(item interface{}, expression string) (interface{}, error) {
Expand Down
3 changes: 2 additions & 1 deletion jsonata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"math"
"os"
Expand All @@ -20,6 +19,8 @@ import (
"time"
"unicode/utf8"

"github.com/stretchr/testify/assert"

"github.com/xiatechs/jsonata-go/jparse"
"github.com/xiatechs/jsonata-go/jtypes"
)
Expand Down
65 changes: 65 additions & 0 deletions processor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package jsonata

import (
"fmt"
)

type JsonataProcessor struct {
tree *Expr
}

func NewProcessor(jsonataString string) (j *JsonataProcessor, err error) {
defer func() { // go-jsonata uses panic fallthrough design so this is necessary
if r := recover(); r != nil {
err = fmt.Errorf("jsonata error: %v", r)
}
}()

jsnt := replaceQuotesAndCommentsInPaths(jsonataString)

e := MustCompile(jsnt)

j = &JsonataProcessor{}

j.tree = e

return j, err
}

// Execute - helper function that lets you parse and run jsonata scripts against an object
func (j *JsonataProcessor) Execute(input interface{}) (output []map[string]interface{}, err error) {
defer func() { // go-jsonata uses panic fallthrough design so this is necessary
if r := recover(); r != nil {
err = fmt.Errorf("jsonata error: %v", r)
}
}()

output = make([]map[string]interface{}, 0)

item, err := j.tree.Eval(input)
if err != nil {
return nil, err
}

if aMap, ok := item.(map[string]interface{}); ok {
output = append(output, aMap)

return output, nil
}

if aList, ok := item.([]interface{}); ok {
for index := range aList {
if aMap, ok := aList[index].(map[string]interface{}); ok {
output = append(output, aMap)
}
}

return output, nil
}

if aList, ok := item.([]map[string]interface{}); ok {
return aList, nil
}

return output, nil
}

0 comments on commit bbead99

Please sign in to comment.