Suppose I have json like this:
[
{
"a": 1,
"b": 2,
"c": 3,
},
{
"a": 1,
"b": 2,
"d": 4
}
]
Is it possible to write a JsonPath which will give me:
{
"a": 1,
"b": 2,
"c": 3,
"d": 4
}
The order of the elements in the list is not sure, however the element names I know beforehand. So I thought I could use JsonPath to select all elements with names 'a', 'b', 'c' or 'd' and put them in a single object. However, I now have tried several things and I start to doubt whether what I want is possible.
For example I can select an object which has an element c with:
$[?(#.c)]
And I can also select just the value of d with something like:
$..d
But I can not find any way to comebine them into a single object. Is what I want possible with JsonPath or is this a limitation of JsonPath?
I think the appropriate way to do this is to remove the second object and apply the difference between them to the first one:
[
{ "op": "remove", "path": "/1" },
{ "op": "add", "path": "/0/d", "value": 4 }
]
Assuming you're OK with polyfilling for IE (it's ES6), use Object.assign:
const object1 = {
a: 1,
b: 2,
c: 3
};
const object2 = {
d: 4
}
const objectMerged = Object.assign({}, object1, object2);
console.log(objectMerged.c);
console.log(objectMerged.d);
For full details, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Browser_compatibility
Related
Consider following array:
{
"A": 100,
"B": 200,
"C": "ccc",
"arr": [
{
"result": ".R1.R3",
"fmt": "%s::%s::baz",
"vals": [".A", ".B"]
},
{
"result": ".R2.R4",
"fmt": "%s/%s",
"vals": [".A", ".C"]
}
]
}
I need to replace keys according some format with values from other keys whose are in strings of some array.
Desired output:
{
"A": 100,
"B": 200,
"C": "ccc",
"R1": {"R3": "100::200::baz"},
"R2": {"R4": "100/ccc"}
}
You didn't specify what language you use in the .vals array items to reference into the document. If you were trying to execute (arbitrary) jq code from there, know that jq cannot do that with code provided as a string value. jq also doesn't provide printf-style substitutions using %s (and others). Therefore, you either need to re-implement a whole bunch of (third-party) functionality, or revert to a simpler scheme describing your references and substitutions.
For the sake of simplicity, this solution just removes the first character (the dot) from the .vals array items and treats the result as top-level field name, and then simply replaces each occurrence of a literal %s with the next value. This should give you an overview of the general technique.
. as $top | reduce .arr[] as $a (del(.arr); .[$a.result] = (
reduce $a.vals[][1:] as $val ($a.fmt; sub("%s"; $top[$val] | #text))
))
{
"A": 100,
"B": 200,
"C": "ccc",
".R1": "100::200::baz",
".R2": "100/ccc"
}
Demo
One quite simple way of improving the reference language is to instead use path expressions jq provides functions for. They are represented as arrays with field names as strings items and array indices as number items. .A would become ["A"], .A[3].B would become ["A",3,"B"], and so on. Thus, assume your input looked like this:
{
"A": 100,
"B": 200,
"C": "ccc",
"arr": [
{
"result": ".R1",
"fmt": "%s::%s::baz",
"vals": [["A"], ["B"]]
},
{
"result": ".R2",
"fmt": "%s/%s",
"vals": [["A"], ["C"]]
}
]
}
Then you could use getpath to evaluate the given path expressions as above:
. as $top | reduce .arr[] as $a (del(.arr); .[$a.result] = (
reduce $a.vals[] as $path ($a.fmt; sub("%s"; $top | getpath($path) | #text))
))
{
"A": 100,
"B": 200,
"C": "ccc",
".R1": "100::200::baz",
".R2": "100/ccc"
}
Demo
Edit: As the question has been modified with the .result value now also being subject to reference interpretation, measures taken for .vals therefore apply to it as well. This implies changing the suggested source document format to use path expressions as in "result": ["R1", "R3"], and changing the assignment in the suggested code from .[$a.result] = ... to setpath($a.result; ...):
{
"A": 100,
"B": 200,
"C": "ccc",
"arr": [
{
"result": ["R1", "R3"],
"fmt": "%s::%s::baz",
"vals": [["A"], ["B"]]
},
{
"result": ["R2", "R4"],
"fmt": "%s/%s",
"vals": [["A"], ["C"]]
}
]
}
. as $top | reduce .arr[] as $a (del(.arr); setpath($a.result;
reduce $a.vals[] as $path ($a.fmt; sub("%s"; $top | getpath($path) | #text))
))
{
"A": 100,
"B": 200,
"C": "ccc",
"R1": {
"R3": "100::200::baz"
},
"R2": {
"R4": "100/ccc"
}
}
Demo
So I have this input to jq:
[
"foo",
{
"a": 1,
"b": 2
},
{
"a": 1,
"b": 3
},
{
"a": 2,
"b": 2
}
]
and I want to select all objects where b is 2 ideally as an array:
[
{
"a": 1,
"b": 2
},
{
"a": 2,
"b": 2
}
]
But the string in the list makes that difficult.
If I try:
.[]| select(.b == 2)
Then I get the error:
jq: error (at /tmp/data.json:14): Cannot index string with string "b"
Any help?
Other answers have suggested using ? which is very good. Another way is to use the built-in objects filter which discards an input if it is not an object:
map(objects | select(.b == 2))
# ^^^^^^^ ^^^^^^^^^^^^^^^
# A B
A: filter out non-objects
B: At this point, we're dealing with objects
Slightly more verbose and perhaps less efficient?
There are a few options.
Ignore errors using ?:
map(select(.b? == 2))
docs, jqplay
Check the value's type in your select filter:
map(select(type == "object" and .b == 2))
docs, jqplay
Filter out non-objects:
map(objects | select(.b == 2))
docs, jqplay
Just as #hobbs suggested, use a ? to skip entries that doesn't have a .b.
As of the second question, I'd use map() so the result is an array as requested:
map(select(.b? == 2))
[
{
"a": 1,
"b": 2
},
{
"a": 2,
"b": 2
}
]
Try it online!
You can just add a ?: .[]| select(.b? == 2).
From the docs:
Optional Object Identifier-Index: .foo?
Just like .foo, but does not output even an error when . is not an array or an object.
Can I add a string literal to a JSONPath selector?
{ "items": [
{ "x": 1 },
{ "x": 2 },
{ "x": 3 },
{ "x": 4 }]
}
$.items[:].x gives...
[
1,
2,
3,
4
]
For example, can I make it return...
[
{ 1 },
{ 2 },
{ 3 },
{ 4 }
]
I want to generate some code that adds items to a dictionary.
As discussed in the comments, this cannot be done using JSONPath (alone) since a path query returns only valid JSON and the target format is not valid. In general, JSONPath is not the right tool here, a JSON transformation using a library like Jolt would be more appropriate; but again, similar to XSLT transformations, we can only create valid output. So, as you already have found out, you would need to use string functions to mingle the code as needed. For instance, a regex substitution could do:
const regex = /(\d+),?/gm;
const str = `[
1,
2,
3,
4
]`;
const subst = `{ $1 },`;
// The substituted value will be contained in the result variable
const result = str.replace(regex, subst);
console.log('Substitution result: ', result);
Let's say I have a json with an array inside. Say that the elements of this array are objects with keys A and B. I would like to remove the B objects on the elements where A objects meet a certain condition.
For example, I would like to remove the B objects where A is greater than 5, transforming
{
"title": "myTitle",
"myArray": [
{
"A": 1,
"B": "foo"
},
{
"A": 4,
"B": "bar"
},
{
"A": 7,
"B": "barfoo"
},
{
"A": 9,
"B": "foobar"
}
]
}
into
{
"title": "myTitle",
"myArray": [
{
"A": 1,
"B": "foo"
},
{
"A": 4,
"B": "bar"
},
{
"A": 7
},
{
"A": 9
}
]
}
The task seems easy enough and if I had't have to keep the A's it would be a simple del(select..) thing. There surely must be an elegant way to do this as well?
Thank you!
You can still use a del(select..) thing.
.myArray[] |= del(select(.A > 5) .B)
demo at jqplay.org
I'm trying to parse json in as3 that has multiple arrays like this:
{
"a1": [{
"a": 1,
"b": "2",
"c": 3
},
{
"a": 1,
"b": "2",
"c": 3
}
],
"a2": [{
"a": 1,
"b": "2",
"c": 3
},
{
"a": 1,
"b": "2",
"c": 3
}
],
"a3": [{
"a": 1,
"b": "2",
"c": 3
},
{
"a": 1,
"b": "2",
"c": 3
}
]
}
I don't really know hot to iterate through it to get the values out of each array though. I assume I'd use a for loop and append it to the end of the array name "a", but I'm not sure how to write this (if that's even the right way to approach it).
What you have is not simply arrays, but a mixture of objects (also known as hashes or associative arrays), as well as lists (arrays).
To iterate both the objects and lists you can use a for each loop:
(assuming your object is called data)
// loop parent object
for each (var parent:Object in data)
{
trace(parent);
// loop child array
for each (var child:Object in parent)
{
trace(child);
// loop grandchild object
for each (var grandchild:Object in child)
{
trace(grandchild);
}
}
}
Please bear in mind that this code is untested and you may need to cast the Object types to more specific types such as (Array)Object in order to properly iterate them.
you could iterate through the JSON like this:
for (var parent:Object in json)
{
for (var child:Object in json[parent] )
{
trace(json[parent][child].a);
trace(json[parent][child].b);
trace(json[parent][child].c);
}
}
you can use "parent" and "child" as keys to parse out the values
Why reinvent the wheel. There are a ton of JSON libraries out there.
This article may help: Mike Chambers - Using JSON with Flex 2 and ActionScript 3