tidwall / gjson

Get JSON values quickly - JSON parser for Go
MIT License
13.95k stars 841 forks source link

Can I add a default value without a key? #295

Open DMLfor opened 1 year ago

DMLfor commented 1 year ago
{
    "name": {
        "first": "Tom",
        "last": "Anderson"
    },
    "age": 37,
    "children": [
        "Sara",
        "Alex",
        "Jack"
    ],
    "fav.movie": "Deer Hunter",
    "friends": [
        {
            "first": "Dale",
            "last": "Murphy",
            "age": 44,
            "nets": [
                "ig",
                "fb",
                "tw"
            ],
            "others": [
                {
                    "k": "v",
                    "v": "k"
                },
                {
                    "k": "v",
                    "v": "k"
                }
            ]
        },
        {
            "last": "Craig",
            "age": 68,
            "nets": [
                "fb",
                "tw"
            ],
            "others": [
                {
                    "v": "k"
                },
                {
                    "k": "v",
                    "v": "k"
                }
            ]
        },
        {
            "first": "Jane",
            "last": "Murphy",
            "age": 47,
            "nets": [
                "ig",
                "tw"
            ],
            "others": [
                {
                    "k": "v",
                    "v": "k"
                },
                {
                    "v": "k"
                }
            ]
        }
    ]
}

Current results:

"friends.#.first"    >> ["Dale", "Jane"]
"friends.#.others.#.k"  >> [["v", "v"], ["v"],["v"]]

With default value:

"friends.#.first"    >> ["Dale", nil, "Jane"]
"friends.#.others.#.k"  >> [["v", "v"], [nil, "v"],["v", nil]]

Thanks:)

ngarg-kr commented 1 year ago

@tidwall ^^

volans- commented 1 year ago

@ngarg-kr I played a bit with multipaths, literals and modifiers and I think I got basically the same behaviour you were looking for.

Query:

friends.#.others.#.[{"k":!null},{k}].@join.@values|#.@flatten

Result:

[["v","v"],[null,"v"],["v",null]]

Explanation:

The [{"k":!null},{k}] part creates a multipaths array with the first item being {"k":null} and the second item being the item with key "k" coming from the others array. That is repeated for each item in the others array and if there is no key "k" an empty object {} is returned instead. So the first part of the query friends.#.others.#.[{"k":!null},{k}] returns:

[[[{"k":null},{"k":"v"}],[{"k":null},{"k":"v"}]],[[{"k":null},{}],[{"k":null},{"k":"v"}]],[[{"k":null},{"k":"v"}],[{"k":null},{}]]]

Then I apply the @join modifier to merge the objects where the last wins, so where there is an empty object we keep the default literal value while where there is a value that one wins. See the dot-vs-pipe section of the docs for more details on the | separator. At that point we get only the values discarding the keys with the @values modifier and finally apply the @flatten modifier to get rid of the additional nested layer of arrays.

Caveats:

I'm not totally sure all the steps are working as expected, I got there with some trial and error, and some modifier got applied to the inner layer a little bit automagically, hence I'm not sure that's the intended behaviour or possibly some bug that could potentially change behaviour in the future.

Other alternatives

Other possible alternatives that are simpler and would just need some additional parsing on your code are:

  1. query: friends.#.others.#.[k] result:
    [[["v"],["v"]],[[],["v"]],[["v"],[]]]

2) query: friends.#.others.#.{k} result:

[[{"k":"v"},{"k":"v"}],[{},{"k":"v"}],[{"k":"v"},{}]]

I also noticed that also using the @values modifier gives the same result of (1) but I'm not sure why or if that's expected: friends.#.others.#.k.@values

litao09h commented 1 year ago

@tidwall @volans- The Same problem, eg:

arr  := gjson.GetBytes(jsonByte, `#(query%"*abc*")#.[resp.vid,resp.uid,resp.text,resp.errorno]`).Array()

Sometimes the key vid\uid may not exist, the length(arr[i]) is not 4 The expected result is a fixed array length of 4, and non-existent keys return null or empty

litao09h commented 1 year ago

@tidwall @volans- The Same problem, eg:

arr  := gjson.GetBytes(jsonByte, `#(query%"*abc*")#.[resp.vid,resp.uid,resp.text,resp.errorno]`).Array()

Sometimes the key vid\uid may not exist, the length(arr[i]) is not 4 The expected result is a fixed array length of 4, and non-existent keys return null or empty

If there is no way to return the default value, I can only append a string to complete the array length

volans- commented 1 year ago

@tidwall @volans- The Same problem, eg:

arr  := gjson.GetBytes(jsonByte, `#(query%"*abc*")#.[resp.vid,resp.uid,resp.text,resp.errorno]`).Array()

Sometimes the key vid\uid may not exist, the length(arr[i]) is not 4 The expected result is a fixed array length of 4, and non-existent keys return null or empty

@litao09h could you provide the JSON input for your query please?

litao09h commented 1 year ago

@tidwall @volans- The Same problem, eg:

arr  := gjson.GetBytes(jsonByte, `#(query%"*abc*")#.[resp.vid,resp.uid,resp.text,resp.errorno]`).Array()

Sometimes the key vid\uid may not exist, the length(arr[i]) is not 4 The expected result is a fixed array length of 4, and non-existent keys return null or empty

@litao09h could you provide the JSON input for your query please?

We converted the log to json

a=11&b=12&c=13&d=14
a=21&b=22&d=24
a=31&c=33&d=34
b=41&c=43&d=44

After log conversion json

[{"a":11,"b":12,"c":13,"d":14},{"a":21,"b":22,"d":14},{"a":31,"c":33,"d":34},{"b":41,"c":43,"d":44}]
volans- commented 1 year ago

@litao09h Sorry but I don't understand. I was asking what is the JSON to which you apply the query #(query%"*abc*")#.[resp.vid,resp.uid,resp.text,resp.errorno]. The one above doesn't seem to match at all your query.

tidwall commented 1 year ago

A default value can be ensured by using the [key,!"default"].0 pattern.

This uses a multipath array to put the key value into the first position of an array, and a literal value !"default" as the second, then uses a .0 path to select the first item in the array. Which will either be key if it exists, or !"default" if key does not exist.

To get the results @DMLfor is looking for in their original question.

friends.#.[first,!null].0        >> ["Dale",null,"Jane"]
friends.#.others.#.[k,!null].0   >> [["v","v"],[null,"v"],["v",null]]
tidwall commented 1 year ago
friends.#.others.#.[k,!null].0

is basically the same as what @volans- used in his example above

friends.#.others.#.[{"k":!null},{k}].@join.@values|#.@flatten

but it may be is a little easier to disect.

litao09h commented 1 year ago

A default value can be ensured by using the [key,!"default"].0 pattern.

This uses a multipath array to put the key value into the first position of an array, and a literal value !"default" as the second, then uses a .0 path to select the first item in the array. Which will either be key if it exists, or !"default" if key does not exist.

To get the results @DMLfor is looking for in their original question.

friends.#.[first,!null].0        >> ["Dale",null,"Jane"]
friends.#.others.#.[k,!null].0   >> [["v","v"],[null,"v"],["v",null]]

That works for me ! Thank you for your patient answer