Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions applier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package dotnotation

import (
"sync"
)

// CompileApplier compiles the dotnotation expression and returns an Applier
func CompileApplier(expr string, op func(interface{}) interface{}) (*Applier, error) {
e, err := compile(expr, op)
return &Applier{e: e}, err
}

var applyPool = sync.Pool{
New: func() any {
p := [2][]interface{}{make([]interface{}, 0, 8), make([]interface{}, 0, 8)}
return &p
},
}

// Applier holds the compiled instructions to apply an operation to the compiled dotnotation path
type Applier struct {
e *dotNotation
}

// Apply applies an operation to the compiled dotnotation path of the given data
func (e *Applier) Apply(data interface{}) {
if e.e.wc == 0 {
e.applyLinear(data)
return
}

pair := applyPool.Get().(*[2][]interface{})
current := append(pair[0], data) // skipcq: CRT-D0001
next := pair[1]

for _, step := range e.e.extractSteps {
switch step.ft {
case stringType:
next = stringTraverse(current, step, next)
case numericType:
next = numericTraverse(current, step, next)
case wildcardType:
next = wildcardTraverse(current, next)
}

if len(next) == 0 {
pair[0] = current[:0]
pair[1] = next
applyPool.Put(pair)
return
}

current, next = next, current[:0]
}

switch e.e.applyStep.ft {
case stringType:
stringApply(current, e.e.applyStep, e.e.op)
case numericType:
numericApply(current, e.e.applyStep, e.e.op)
case wildcardType:
wildcardApply(current, e.e.op)
}

pair[0] = current[:0]
pair[1] = next
applyPool.Put(pair)
}

func (e *Applier) applyLinear(data interface{}) { // skipcq: GO-R1005
current := data

for _, step := range e.e.extractSteps {
switch step.ft {
case stringType:
m, ok := current.(map[string]interface{})
if !ok {
return
}
v, exists := m[step.key]
if !exists {
return
}
current = v
case numericType:
if m, ok := current.(map[string]interface{}); ok {
v, exists := m[step.key]
if !exists {
return
}
current = v
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continue and avoid the else

continue
}
if arr, ok := current.([]interface{}); ok {
if step.index >= len(arr) {
return
}
current = arr[step.index]
}
}
}

switch e.e.applyStep.ft {
case stringType:
if m, ok := current.(map[string]interface{}); ok {
if v, exists := m[e.e.applyStep.key]; exists {
m[e.e.applyStep.key] = e.e.op(v)
}
}
case numericType:
if m, ok := current.(map[string]interface{}); ok {
if v, exists := m[e.e.applyStep.key]; exists {
m[e.e.applyStep.key] = e.e.op(v)
}
return
}
if arr, ok := current.([]interface{}); ok {
if e.e.applyStep.index < len(arr) {
arr[e.e.applyStep.index] = e.e.op(arr[e.e.applyStep.index])
}
}
case wildcardType:
switch v := current.(type) {
case []interface{}:
for i, vv := range v {
v[i] = e.e.op(vv)
}
case map[string]interface{}:
for k, vv := range v {
v[k] = e.e.op(vv)
}
}
}
}

func wildcardApply(current []interface{}, op func(interface{}) interface{}) {
for _, n := range current {
switch v := n.(type) {
case []interface{}:
for i, vv := range v {
v[i] = op(vv)
}
case map[string]interface{}:
for i, vv := range v {
v[i] = op(vv)
}
}
}
}

func numericApply(current []interface{}, step step, op func(interface{}) interface{}) {
for _, n := range current {
if m, ok := n.(map[string]interface{}); ok {
if v, exists := m[step.key]; exists {
m[step.key] = op(v)
}
continue
}
if arr, ok := n.([]interface{}); ok {
if step.index < len(arr) {
arr[step.index] = op(arr[step.index])
}
}
}
}

func stringApply(current []interface{}, step step, op func(interface{}) interface{}) {
for _, n := range current {
if m, ok := n.(map[string]interface{}); ok {
if v, exists := m[step.key]; exists {
m[step.key] = op(v)
}
}
}
}
113 changes: 23 additions & 90 deletions dotnotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,6 @@ type step struct {
ft fieldType
key string
index int
o func(interface{}) interface{}
}

// Applier holds the compiled instructions to apply an operation to the compiled dotnotation path
type Applier struct {
e *dotNotation
}

// Apply applies an operation to the compiled dotnotation path of the given data
func (a *Applier) Apply(v interface{}) {
a.e.Evaluate(v)
}

// Extractor holds the compiled instructions to extract the compiled dotnotation path values
type Extractor struct {
e *dotNotation
}

// Extract extracts the values in the compiled dotnotation path of the given data
func (e *Extractor) Extract(v interface{}) []interface{} {
return e.e.Evaluate(v)
}

// CompileExtractor compiles the dotnotation expression and returns an Extractor
func CompileExtractor(expr string) (*Extractor, error) {
e, err := compile(expr, nil)
return &Extractor{e: e}, err
}

// CompileApplier compiles the dotnotation expression and returns an Applier
func CompileApplier(expr string, op func(interface{}) interface{}) (*Applier, error) {
e, err := compile(expr, op)
return &Applier{e: e}, err
}

func compile(expr string, op func(interface{}) interface{}) (*dotNotation, error) {
Expand All @@ -61,6 +28,7 @@ func compile(expr string, op func(interface{}) interface{}) (*dotNotation, error
parts := strings.Split(expr, ".")
steps := make([]step, 0, len(parts))
wc := 0

for _, p := range parts {
idx, err := strconv.Atoi(p)
isIndex := err == nil && idx >= 0
Expand All @@ -78,64 +46,41 @@ func compile(expr string, op func(interface{}) interface{}) (*dotNotation, error
}
}

steps[len(steps)-1].o = op
// if there's a wildcard on last step, we remove it from counter so it goes through Linear paths anyway
// since it does not need to allocate a slice while traversing the main path
if steps[len(steps)-1].ft == wildcardType {
wc--
}

var applyStep step
if op != nil {
applyStep = steps[len(steps)-1]
steps = steps[:len(steps)-1]
}

return &dotNotation{
steps: steps,
wc: wc,
extractSteps: steps,
applyStep: applyStep,
op: op,
wc: wc,
}, nil
}

type dotNotation struct {
steps []step
wc int
extractSteps []step
applyStep step
op func(interface{}) interface{}
wc int
}

func (e *dotNotation) Evaluate(data interface{}) []interface{} {
current := []interface{}{data}
next := make([]interface{}, 0, 4*e.wc+1)

for _, step := range e.steps {
next = next[:0]

switch step.ft {
case stringType:
next = stringTraverse(current, step, next)
case numericType:
next = numericTraverse(current, step, next)
case wildcardType:
next = wildcardTraverse(current, step, next)
}

if len(next) == 0 {
return next
}

current, next = next, current
}
return current
}

func wildcardTraverse(current []interface{}, step step, next []interface{}) []interface{} {
func wildcardTraverse(current []interface{}, next []interface{}) []interface{} {
for _, n := range current {
switch v := n.(type) {
case []interface{}:
if step.o != nil {
for i := range v {
v[i] = step.o(v[i])
}
continue
}
next = append(next, v...)
case map[string]interface{}:
if step.o != nil {
for i := range v {
v[i] = step.o(v[i])
}
continue
}
for i := range v {
next = append(next, v[i])
for _, vv := range v {
next = append(next, vv)
}
}
}
Expand All @@ -146,20 +91,12 @@ func numericTraverse(current []interface{}, step step, next []interface{}) []int
for _, n := range current {
if m, ok := n.(map[string]interface{}); ok {
if v, exists := m[step.key]; exists {
if step.o != nil {
m[step.key] = step.o(v)
continue
}
next = append(next, v)
}
continue
}
if arr, ok := n.([]interface{}); ok {
if step.index < len(arr) {
if step.o != nil {
arr[step.index] = step.o(arr[step.index])
continue
}
next = append(next, arr[step.index])
}
}
Expand All @@ -171,10 +108,6 @@ func stringTraverse(current []interface{}, step step, next []interface{}) []inte
for _, n := range current {
if m, ok := n.(map[string]interface{}); ok {
if v, exists := m[step.key]; exists {
if step.o != nil {
m[step.key] = step.o(v)
continue
}
next = append(next, v)
}
}
Expand Down
Loading
Loading