ohler55 / ojg

Optimized JSON for Go
MIT License
857 stars 49 forks source link

Working with a collection of JSONPaths simultaneously? #122

Closed paulo-raca closed 1 year ago

paulo-raca commented 1 year ago

Hello, and thank you for this great library!

I'm trying to apply multiple JSONPaths in a JSON and I'm having a few difficulties, please help me out

Thanks a lot!

Use case

I'm using multiple JSONPaths to select/deselect parts of my datastructure that need processing (modifying, filtering, etc).

It basically looks like this:

type JsonPathSelector struct {
    select []jp.Expr
    deselect []jp.Expr
}

When it runs, it wraps all the matched JSON subtrees in "tags" that tells whenever that subtree is enabled or disabled:

type JsonPathSelectorTag struct {
    value   any
    selected bool
}

For instance, I use it like this:

input := map[string]any{
    "foo": []any{1, 2, 3},
    "bar": 4,
}
filter := {
   select: ["$.foo[0]"],
   deselect: ["$.foo"],
}
output := JsonPathFilter.AddTags(input)

expectedOutput := map[string]any{
    "foo": JsonPathSelectorTag{
        selected: false,
        value: []any{
            JsonPathSelectorTag{
                selected: true,
                value: 1,
            }, 
            2, 
            3
        },
    },
    "bar": 4,
}

Problem

My current implementation uses jp.Expr.Modify() serially to add tags:

func (f *JsonPathSelector) AddTags(data any) (any, error) {
    for _, expr := range f.select {
        data = expr.Modify(data, func(element any) (altered any, changed bool) {
            return JsonFilterTag{element, true}, true
        })
    }
    for _, expr := range f.deselect {
        data = expr.Modify(data, func(element any) (altered any, changed bool) {
            return JsonFilterTag{element, false}, true
        })
    }
    return data, nil
}

Unfortunately, since it modifies the data at each step, the processing order affects the output.

E.g., if $.a.b is executed before $.a both subtrees are tagged. However if the order is reversed, $.a is tagged, but $.a.b (since .b won't match the JsonFilterTag)

Possible Solutions

I can think of 2 ways to work around this issue, and either one works fine with me. Or maybe there is something else I'm missing?

Supporting "Tag" objects:

Everything would work for me if JSONPath somehow ignored JsonPathSelectorTag. It seems reasonably to add a special structure where I can attach arbitrary data and that is also ignored by JsonPath

type Tag struct {
    contents   any
    tag any
}

This maps very well with my current implementation and seems easier to implement in the library

Supporting operations on multiple paths simultaneously

type MultiExpr []jp.Expr

multiExpr = ["$.foo[0]", "$.foo"]
data = multiExpr.Modify(data, func(element any, exprIndex []int) (altered any, changed bool) {
    return element, false
})

This seems a bit more complicate to implement, but also more flexible. Note that Modify() would need an extra argument to specify which expressions matched each element.

ohler55 commented 1 year ago

I'm not sure either of the proposed solutions solves the issue. If I can summarize, you would like a set of modifications that to be applied in arbitrary order and are looking for a way to specify the desired order. It seems like the simple solution is to just apply the modification in the desired order. I'm probably missing some key part of this. I apologize if I missed the point.

ohler55 commented 1 year ago

Is this a dead issue?