1 #include <stdlib.h>
2 #include <string.h>
3 #include "lib/mlrutil.h"
4 #include "containers/mlhmmv.h"
5 #include "output/lrec_writers.h"
6
7 typedef struct _lrec_writer_json_state_t {
8 unsigned long long counter;
9 char* output_json_flatten_separator;
10
11 int json_quote_int_keys;
12 int json_quote_non_string_values;
13 char* line_indent;
14 char* before_records_at_start_of_stream1;
15 char* between_records_after_start_of_stream;
16 char* after_records_at_end_of_stream1;
17 char* line_term;
18 int stack_vertically;
19
20 } lrec_writer_json_state_t;
21
22 static void lrec_writer_json_free(lrec_writer_t* pwriter, context_t* pctx);
23 static void lrec_writer_json_process(void* pvstate, FILE* output_stream, lrec_t* prec,
24 char* before_or_after_records, char* line_term);
25 static void lrec_writer_json_process_auto_line_term_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
26 context_t* pctx);
27 static void lrec_writer_json_process_auto_line_term_no_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
28 context_t* pctx);
29 static void lrec_writer_json_process_nonauto_line_term_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
30 context_t* pctx);
31 static void lrec_writer_json_process_nonauto_line_term_no_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
32 context_t* pctx);
33
34 // ----------------------------------------------------------------
lrec_writer_json_alloc(int stack_vertically,int wrap_json_output_in_outer_list,int json_quote_int_keys,int json_quote_non_string_values,char * output_json_flatten_separator,char * line_term)35 lrec_writer_t* lrec_writer_json_alloc(int stack_vertically, int wrap_json_output_in_outer_list,
36 int json_quote_int_keys, int json_quote_non_string_values,
37 char* output_json_flatten_separator, char* line_term)
38 {
39 lrec_writer_t* plrec_writer = mlr_malloc_or_die(sizeof(lrec_writer_t));
40
41 lrec_writer_json_state_t* pstate = mlr_malloc_or_die(sizeof(lrec_writer_json_state_t));
42 pstate->json_quote_int_keys = json_quote_int_keys;
43 pstate->json_quote_non_string_values = json_quote_non_string_values;
44 pstate->counter = 0;
45 pstate->output_json_flatten_separator = output_json_flatten_separator;
46
47 // xxx pending reworked JSON-output logic (not always ending in LF; needing fflush),
48 // to be implemented someday if ever. Workaround: pipe output to "jq .".
49 //pstate->line_indent = wrap_json_output_in_outer_list ? " " : "";
50 pstate->line_indent = wrap_json_output_in_outer_list ? "" : "";
51 pstate->before_records_at_start_of_stream1 = wrap_json_output_in_outer_list ? "[" : "";
52 pstate->between_records_after_start_of_stream = wrap_json_output_in_outer_list ? "," : "";
53 pstate->after_records_at_end_of_stream1 = wrap_json_output_in_outer_list ? "]" : "";
54 pstate->line_term = line_term;
55 pstate->stack_vertically = stack_vertically;
56
57 plrec_writer->pvstate = (void*)pstate;
58 if (streq(line_term, "auto")) {
59 plrec_writer->pprocess_func = wrap_json_output_in_outer_list
60 ? lrec_writer_json_process_auto_line_term_wrap
61 : lrec_writer_json_process_auto_line_term_no_wrap;
62 } else {
63 plrec_writer->pprocess_func = wrap_json_output_in_outer_list
64 ? lrec_writer_json_process_nonauto_line_term_wrap
65 : lrec_writer_json_process_nonauto_line_term_no_wrap;
66 }
67 plrec_writer->pfree_func = lrec_writer_json_free;
68
69 return plrec_writer;
70 }
71
lrec_writer_json_free(lrec_writer_t * pwriter,context_t * pctx)72 static void lrec_writer_json_free(lrec_writer_t* pwriter, context_t* pctx) {
73 free(pwriter->pvstate);
74 free(pwriter);
75 }
76
77 // ----------------------------------------------------------------
lrec_writer_json_process_auto_line_term_wrap(void * pvstate,FILE * output_stream,lrec_t * prec,context_t * pctx)78 static void lrec_writer_json_process_auto_line_term_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
79 context_t* pctx)
80 {
81 lrec_writer_json_process(pvstate, output_stream, prec, pctx->auto_line_term, pctx->auto_line_term);
82 }
83
lrec_writer_json_process_auto_line_term_no_wrap(void * pvstate,FILE * output_stream,lrec_t * prec,context_t * pctx)84 static void lrec_writer_json_process_auto_line_term_no_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
85 context_t* pctx)
86 {
87 lrec_writer_json_process(pvstate, output_stream, prec, "", pctx->auto_line_term);
88 }
89
lrec_writer_json_process_nonauto_line_term_wrap(void * pvstate,FILE * output_stream,lrec_t * prec,context_t * pctx)90 static void lrec_writer_json_process_nonauto_line_term_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
91 context_t* pctx)
92 {
93 lrec_writer_json_state_t* pstate = pvstate;
94 lrec_writer_json_process(pvstate, output_stream, prec, pstate->line_term, pstate->line_term);
95 }
96
lrec_writer_json_process_nonauto_line_term_no_wrap(void * pvstate,FILE * output_stream,lrec_t * prec,context_t * pctx)97 static void lrec_writer_json_process_nonauto_line_term_no_wrap(void* pvstate, FILE* output_stream, lrec_t* prec,
98 context_t* pctx)
99 {
100 lrec_writer_json_state_t* pstate = pvstate;
101 lrec_writer_json_process(pvstate, output_stream, prec, "", pstate->line_term);
102 }
103
lrec_writer_json_process(void * pvstate,FILE * output_stream,lrec_t * prec,char * before_or_after_records,char * line_term)104 static void lrec_writer_json_process(void* pvstate, FILE* output_stream, lrec_t* prec,
105 char* before_or_after_records, char* line_term)
106 {
107 lrec_writer_json_state_t* pstate = pvstate;
108 if (prec != NULL) { // not end of record stream
109 if (pstate->counter++ == 0) {
110 fputs(pstate->before_records_at_start_of_stream1, output_stream);
111 fputs(before_or_after_records, output_stream);
112 } else {
113 fputs(pstate->between_records_after_start_of_stream, output_stream);
114 }
115
116 // Use the mlhmmv printer since it naturally handles Miller-to-JSON key deconcatenation:
117 // e.g. 'a:x=1,a:y=2' maps to '{"a":{"x":1,"y":2}}'.
118 mlhmmv_root_t* pmap = mlhmmv_root_alloc();
119
120 char* sep = pstate->output_json_flatten_separator;
121 int seplen = strlen(sep);
122
123 for (lrece_t* pe = prec->phead; pe != NULL; pe = pe->pnext) {
124 // strdup since strmsep is destructive and CSV/PPRINT header fields
125 // are shared across multiple records
126 char* lkey = mlr_strdup_or_die(pe->key);
127 char* lvalue = pe->value;
128
129 sllmv_t* pmvkeys = sllmv_alloc();
130 char* walker = lkey;
131 char* piece = NULL;
132 while ((piece = mlr_strmsep(&walker, sep, seplen)) != NULL) {
133 mv_t mvkey = mv_from_string(piece, NO_FREE);
134 sllmv_append_no_free(pmvkeys, &mvkey);
135 }
136 mv_t mvval = mv_from_string(lvalue, NO_FREE);
137 mlhmmv_root_put_terminal(pmap, pmvkeys, &mvval);
138 sllmv_free(pmvkeys);
139 free(lkey);
140 }
141
142 if (pstate->stack_vertically)
143 mlhmmv_root_print_json_stacked(pmap, pstate->json_quote_int_keys, pstate->json_quote_non_string_values,
144 pstate->line_indent, line_term, output_stream);
145 else
146 mlhmmv_root_print_json_single_lines(pmap, pstate->json_quote_int_keys,
147 pstate->json_quote_non_string_values, line_term, output_stream);
148
149 mlhmmv_root_free(pmap);
150
151 lrec_free(prec); // end of baton-pass
152
153 } else { // end of record stream
154 fputs(pstate->after_records_at_end_of_stream1, output_stream);
155 fputs(before_or_after_records, output_stream);
156 }
157 }
158