Skip to content

Commit da6d62b

Browse files
benbernardclaude
andcommitted
feat: add expandjson operation to parse JSON strings in record fields
New transform operation that expands string fields containing JSON into parsed JSON values. Supports --key for specific fields, auto-detection of all JSON-like strings, and --recursive for nested JSON expansion. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent c153158 commit da6d62b

14 files changed

Lines changed: 518 additions & 12 deletions

File tree

docs/reference/annotate.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Evaluate an expression on each record and cache the resulting changes by key gro
1717
| Flag | Description |
1818
|------|-------------|
1919
| `--keys` / `-k` `<keys>` | Keys to match records by. May be specified multiple times. May be a keygroup or keyspec. **(required)** |
20+
| `--expr` / `-e` `<code>` | Inline expression to evaluate (alternative to positional argument). |
2021

2122
## Examples
2223

docs/reference/assert.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Asserts that every record in the stream must pass the given expression. The expr
1818
|------|-------------|
1919
| `--diagnostic` / `-d` `<text>` | Include this diagnostic string in any failed assertion errors. |
2020
| `--verbose` / `-v` | Verbose output for failed assertions; dumps the current record. |
21+
| `--expr` / `-e` `<code>` | Inline expression to evaluate (alternative to positional argument). |
2122

2223
## Examples
2324

docs/reference/collate.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,22 @@ Take records, grouped together by --keys, and compute statistics (like average,
1919
| `--key` / `-k` `<keys>` | Comma-separated list of key fields for grouping. May be a key spec or key group. |
2020
| `--aggregator` / `-a` `<aggregators>` | Colon-separated aggregator specification in the form [&lt;fieldname&gt;=]&lt;aggregator&gt;[,&lt;arguments&gt;]. |
2121
| `--dlaggregator` / `-A` `<name>=<expression>` | Domain language aggregator in the form name=expression. The expression is evaluated as JavaScript to produce an aggregator. |
22-
| `--mr-agg` `<string>` | MapReduce aggregator: specify 4 times for name, map snippet, reduce snippet, and squish snippet. |
23-
| `--ii-agg` `<string>` | InjectInto aggregator: specify 4 times for name, initial snippet, combine snippet, and squish snippet. |
24-
| `--dlkey` `<name>=<expression>` | Domain language key: name=expression where the expression evaluates as a valuation. |
22+
| `--mr-agg` `<name> <map> <reduce> <squish>` | MapReduce aggregator: takes 4 arguments: name, map snippet, reduce snippet, squish snippet. |
23+
| `--ii-agg` `<name> <initial> <combine> <squish>` | InjectInto aggregator: takes 4 arguments: name, initial snippet, combine snippet, squish snippet. |
24+
| `--dlkey` / `-K` `<name>=<expression>` | Domain language key: name=expression where the expression evaluates as a valuation. |
2525
| `--incremental` / `-i` | Output a record every time an input record is added to a clump (instead of every time a clump is flushed). |
2626
| `--bucket` | Output one record per clump (default). |
2727
| `--no-bucket` | Output one record for each record that went into the clump. |
28-
| `--adjacent` | Only group together adjacent records. Avoids spooling records into memory. |
29-
| `--size` / `-n` `<number>` | Number of running clumps to keep. |
28+
| `--adjacent` / `-1` | Only group together adjacent records. Avoids spooling records into memory. |
29+
| `--size` / `--sz` / `-n` `<number>` | Number of running clumps to keep. |
3030
| `--cube` | Enable cube mode: output all key combinations with ALL placeholders. |
3131
| `--clumper` / `-c` `<spec>` | Clumper specification (e.g. keylru,field,size or keyperfect,field or window,size). |
32+
| `--dlclumper` / `-C` `<expression>` | Domain language clumper specification. |
3233
| `--perfect` | Group records regardless of order (perfect hashing). |
3334
| `--list-aggregators` | List available aggregators and exit. |
3435
| `--show-aggregator` `<name>` | Show details of a specific aggregator and exit. |
36+
| `--list-clumpers` | List available clumpers and exit. |
37+
| `--show-clumper` `<name>` | Show details of a specific clumper and exit. |
3538

3639
## Examples
3740

docs/reference/expandjson.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# expandjson
2+
3+
Expand JSON strings embedded in record fields into actual JSON values.
4+
5+
## Synopsis
6+
7+
```bash
8+
recs expandjson [options] [files...]
9+
```
10+
11+
## Description
12+
13+
Expand JSON strings embedded in record fields into actual JSON values. When a field contains a string that is valid JSON (object, array, etc.), this operation parses it and replaces the string with the parsed structure. With no --key options, all top-level string fields that look like JSON are expanded.
14+
15+
## Options
16+
17+
| Flag | Description |
18+
|------|-------------|
19+
| `--key` / `-k` `<key>` | Key containing a JSON string to expand. May be a keyspec. May be specified multiple times for multiple keys. |
20+
| `--recursive` / `-r` | Recursively expand JSON strings found in nested values after initial expansion. |
21+
22+
## Examples
23+
24+
### Expand a metadata field containing a JSON string
25+
```bash
26+
recs expandjson --key metadata
27+
```
28+
29+
Input:
30+
```json
31+
{"name":"alice","metadata":"{\"role\":\"admin\",\"level\":3}"}
32+
```
33+
34+
Output:
35+
```json
36+
{"name":"alice","metadata":{"role":"admin","level":3}}
37+
```
38+
39+
### Recursively expand nested JSON strings
40+
```bash
41+
recs expandjson -r --key payload
42+
```
43+
44+
### Expand all JSON-like string fields automatically
45+
```bash
46+
recs expandjson
47+
```
48+
49+
## See Also
50+
51+
- [flatten](./flatten)
52+
- [eval](./eval)

docs/reference/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ These commands reshape, filter, sort, and aggregate records.
3636
| [decollate](./decollate) | Reverse of collate: takes a single record and produces multiple records using deaggregators |
3737
| [delta](./delta) | Transforms absolute values into deltas between adjacent records |
3838
| [eval](./eval) | Evaluate an expression on each record and print the result as a line of text |
39+
| [expandjson](./expandjson) | Expand JSON strings embedded in record fields into actual JSON values |
3940
| [flatten](./flatten) | Flatten nested hash/array structures in records into top-level fields |
4041
| [generate](./generate) | Execute an expression for each record to generate new records |
4142
| [grep](./grep) | Filter records where an expression evaluates to true |

docs/reference/multiplex.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ Take records, grouped together by --keys, and run a separate operation instance
1717
| Flag | Description |
1818
|------|-------------|
1919
| `--key` / `-k` `<keys>` | Comma-separated list of key fields for grouping. May be a key spec or key group. |
20+
| `--dlkey` / `-K` `<name>=<expression>` | Domain language key: name=expression where the expression evaluates as a valuation. |
2021
| `--line-key` / `-L` `<key>` | Use the value of this key as line input for the nested operation (rather than the entire record). Use with recs-from* operations generally. |
21-
| `--adjacent` | Only group together adjacent records. Avoids spooling records into memory. |
22-
| `--size` `<number>` | Number of running clumps to keep. |
22+
| `--adjacent` / `-1` | Only group together adjacent records. Avoids spooling records into memory. |
23+
| `--size` / `--sz` / `-n` `<number>` | Number of running clumps to keep. |
2324
| `--cube` | Enable cube mode. |
25+
| `--clumper` / `-c` `<spec>` | Clumper specification (e.g. keylru,field,size or keyperfect,field). |
26+
| `--dlclumper` `<expression>` | Domain language clumper specification. |
27+
| `--list-clumpers` | List available clumpers and exit. |
28+
| `--show-clumper` `<name>` | Show details of a specific clumper and exit. |
2429

2530
## Examples
2631

man/man1/recs-expandjson.1

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.TH RECS\-EXPANDJSON 1 "2026-02-22" "recs 0.1.0" "RecordStream Manual"
2+
3+
.SH NAME
4+
recs\-expandjson \- Expand JSON strings embedded in record fields into actual JSON values
5+
6+
.SH SYNOPSIS
7+
.B recs expandjson [options] [files...]
8+
9+
.SH DESCRIPTION
10+
Expand JSON strings embedded in record fields into actual JSON values. When a field contains a string that is valid JSON (object, array, etc.), this operation parses it and replaces the string with the parsed structure. With no \-\-key options, all top\-level string fields that look like JSON are expanded.
11+
.PP
12+
13+
.SH OPTIONS
14+
.TP
15+
\fB--key\fR, \fB-k\fR \fI<key>\fR
16+
Key containing a JSON string to expand. May be a keyspec. May be specified multiple times for multiple keys.
17+
.TP
18+
\fB--recursive\fR, \fB-r\fR
19+
Recursively expand JSON strings found in nested values after initial expansion.
20+
21+
.SH EXAMPLES
22+
Expand a metadata field containing a JSON string
23+
.PP
24+
.RS 4
25+
.nf
26+
\fBrecs expandjson --key metadata\fR
27+
.fi
28+
.RE
29+
.PP
30+
Input:
31+
.RS 4
32+
.nf
33+
{"name":"alice","metadata":"{\\"role\\":\\"admin\\",\\"level\\":3}"}
34+
.fi
35+
.RE
36+
.PP
37+
Output:
38+
.RS 4
39+
.nf
40+
{"name":"alice","metadata":{"role":"admin","level":3}}
41+
.fi
42+
.RE
43+
44+
Recursively expand nested JSON strings
45+
.PP
46+
.RS 4
47+
.nf
48+
\fBrecs expandjson -r --key payload\fR
49+
.fi
50+
.RE
51+
52+
Expand all JSON\-like string fields automatically
53+
.PP
54+
.RS 4
55+
.nf
56+
\fBrecs expandjson\fR
57+
.fi
58+
.RE
59+
60+
.SH SEE ALSO
61+
\fBrecs\-flatten\fR(1), \fBrecs\-eval\fR(1)
62+
63+
.SH AUTHOR
64+
Benjamin Bernard <[email protected]>
65+

man/man1/recs.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ Transforms absolute values into deltas between adjacent records
7979
\fBrecs eval\fR
8080
Evaluate an expression on each record and print the result as a line of text
8181
.TP
82+
\fBrecs expandjson\fR
83+
Expand JSON strings embedded in record fields into actual JSON values
84+
.TP
8285
\fBrecs flatten\fR
8386
Flatten nested hash/array structures in records into top\-level fields
8487
.TP

src/cli/dispatcher.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { SubstreamOperation } from "../operations/transform/substream.ts";
3939
import { JoinOperation } from "../operations/transform/join.ts";
4040
import { CollateOperation } from "../operations/transform/collate.ts";
4141
import { DecollateOperation } from "../operations/transform/decollate.ts";
42+
import { ExpandJsonOperation } from "../operations/transform/expandjson.ts";
4243
import { ChainOperation } from "../operations/transform/chain.ts";
4344
import { MultiplexOperation } from "../operations/transform/multiplex.ts";
4445

@@ -82,6 +83,7 @@ const operationRegistry = new Map<string, OpConstructor>([
8283
["topn", TopnOperation],
8384
["assert", AssertOperation],
8485
["delta", DeltaOperation],
86+
["expandjson", ExpandJsonOperation],
8587
["flatten", FlattenOperation],
8688
["annotate", AnnotateOperation],
8789
["generate", GenerateOperation],

src/cli/operation-registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { documentation as collate } from "../operations/transform/collate.ts";
3232
import { documentation as decollate } from "../operations/transform/decollate.ts";
3333
import { documentation as delta } from "../operations/transform/delta.ts";
3434
import { documentation as eval_ } from "../operations/transform/eval.ts";
35+
import { documentation as expandjson } from "../operations/transform/expandjson.ts";
3536
import { documentation as flatten } from "../operations/transform/flatten.ts";
3637
import { documentation as generate } from "../operations/transform/generate.ts";
3738
import { documentation as grep } from "../operations/transform/grep.ts";
@@ -79,6 +80,7 @@ export const allDocs: CommandDoc[] = [
7980
decollate,
8081
delta,
8182
eval_,
83+
expandjson,
8284
flatten,
8385
generate,
8486
grep,

0 commit comments

Comments
 (0)