PaesslerAG / jsonpath

BSD 3-Clause "New" or "Revised" License
172 stars 37 forks source link

Expression for testing root object for key #16

Closed FrankReh closed 5 years ago

FrankReh commented 5 years ago

A very interesting parser. I'm trying to use it to parse log files that contain objects at the top level where the object structure being logged is different from line to line. The log file is not one valid JSON object, but rather a series of valid JSON objects. I have logic that parses one object byte slice at a time, and unmarshals to map[string]interface{}.

But I haven't found a jsonpath syntax for testing the root object for the property of having a particular key. I believe I've looked through all the unit tests to better understand the syntax and saw no examples where the root was an object and the test was for the existence of a key. But there also wasn't anything in the documentation (that I found) to explain why this shouldn't be possible.

I could get things working if I read the entire file into memory and created an array out of them ([]interface{}) or if I wrapped each object in a single entry array but now I'm curious whether this should even be possible given the description of a jsonpath.

I've tried a number of things, most nonsensical anyway, but here are a few attempts with their corresponding errors.

package play_test

import (
    "encoding/json"
    "fmt"
    "strings"
    "testing"

    "github.com/PaesslerAG/jsonpath"
)

func TestPlay(t *testing.T) {
    for idx, c := range []struct{ expr, data, expect string }{
        {
            // This works. No surprise.
            expr:   `$.ip`,
            data:   `{"ip": "8.8.8.8", "action": "dns"}`,
            expect: `result: 8.8.8.8`,
        },
        {
            // This one doesn't. It is not legal to request a key that does not exist.
            expr:   `$.ip`,
            data:   `{"action": "dns"}`,
            expect: `error : unknown key ip`,
        },
        {
            // Same error.
            expr:   `($.ip)`,
            data:   `{"action": "dns"}`,
            expect: `error : unknown key ip`,
        },
        {
            // Other illegal syntax.
            expr: `?($.ip != "")`,
            data: `{"action": "dns"}`,
            expect: `error : parsing error: ?($.ip != "")   :1:1 - 1:2 unexpected "?" while scanning extensions`,
        },
        {
            // So how to test whether a key exists?
            // Here the syntax is legal but the result is an empty array.
            expr:   `$[?($.ip)]`,
            data:   `{"ip": "8.8.8.8, "action": "dns"}`,
            expect: `result: []`,
        },
        {
            // This doesn't parse correctly.
            expr: `("ip" in $)`,
            data: `{"ip": "8.8.8.8, "action": "dns"}`,
            expect: `error : parsing error: ("ip" in $) :1:7 - 1:9 unexpected Ident while scanning parentheses expected ")"`,
        },
        {
            // This doesn't parse correctly either.
            expr: `$[?("ip" in $)]`,
            data: `{"ip": "8.8.8.8, "action": "dns"}`,
            expect: `error : parsing error: $[?("ip" in $)] :1:10 - 1:12 unexpected Ident while scanning parentheses expected ")"`,
        },
    } {
        v := interface{}(nil)
        json.Unmarshal([]byte(c.data), &v)
        result, err := jsonpath.Get(c.expr, v)
        var buf strings.Builder
        if err == nil {
            fmt.Fprint(&buf, "result: ", result)
        } else {
            fmt.Fprint(&buf, "error : ", err)
        }
        got := buf.String()
        if c.expect != got {
            t.Errorf("idx %d failed comparison", idx)
            t.Log("expect:", c.expect)
            t.Log("got   :", got)
        }
    }

}
FrankReh commented 5 years ago

Sorry for the bother. Reading through things more, I learned how easy it is to add a function to the language. So I can use a function that tests for the key existence in an object.