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