# A command for cherry-picking values from a record key recursively
export def "flatten record-paths" [
--separator (-s): string = "." # The separator to use when chaining paths
] {
let input = $in
if ($input | describe) !~ "record" {
error make {msg: "The record-paths command expects a record"}
}
$input | flatten-record-paths $separator
}
def flatten-record-paths [separator: string, ctx?: string] {
let input = $in
match ($input | describe-primitive) {
"record" => {
$input
| items { |key, value|
let path = if $ctx == null { $key } else { [$ctx $key] | str join $separator }
{path: $path, value: $value}
}
| reduce -f [] { |row, acc|
$acc
| append ($row.value | flatten-record-paths $separator $row.path)
| flatten
}
},
"list" => {
$input
| enumerate
| each { |e|
{path: ([$ctx $e.index] | str join $separator), value: $e.item}
}
},
"table" => {
$input | enumerate | each { |r| $r.item | flatten-record-paths $separator ([$ctx $r.index] | str join $separator) }
}
"block" | "closure" => {
error make {msg: "Unexpected type"}
},
_ => {
{path: $ctx, value: $input}
},
}
}
#[test]
def test_record_path [] {
assert equal ({a: 1} | flatten record-paths) [{path: "a", value: 1}]
assert equal ({a: 1, b: [2 3]} | flatten record-paths) [[path value]; [a 1] ["b.0" 2] ["b.1" 3]]
assert equal ({a: 1, b: {c: 2}} | flatten record-paths) [[path value]; [a 1] ["b.c" 2]]
assert equal ({a: {b: {c: null}}} | flatten record-paths -s "->") [[path value]; ["a->b->c" null]]
}
# A command for walking through a complex data structure and transforming its values recursively
export def filter-map [mapping_fn: closure] {
let input = $in
match ($input | describe-primitive) {
"record" => {
$input
| items { |key, value|
{key: $key, value: ($value | filter-map $mapping_fn)}
}
| transpose -rd
},
"list" => {
$input
| each { |value|
$value | filter-map $mapping_fn
}
},
"table" | "block" | "closure" => { error make {msg: "unimplemented"} },
_ => {
do $mapping_fn $input
},
}
}
#[test]
def test_filtermap [] {
assert equal ({a: 42} | filter-map {|x| if ($x | describe) == "int" { $x * 2 } else { $x }}) {a: 84}
assert equal ({a: 1, b: 2, c: {d: 3}} | filter-map {|x| if ($x | describe) == "int" { $x * 2 } else { $x }}) {a: 2, b: 4, c: {d: 6}}
assert equal ({a: 1, b: "2", c: {d: 3}} | filter-map {|x| if ($x | describe) == "int" { $x * 2 } else { $x }}) {a: 2, b: "2", c: {d: 6}}
}
```
## Credits
All jq examples were taken from the [The Ultimate Interactive JQ Guide](https://ishan.page/blog/2023-11-06-jq-by-example/).