-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathjson2csv.pl
executable file
·125 lines (98 loc) · 2.84 KB
/
json2csv.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/perl
# Author: Daniel "Trizen" Șuteu
# Date: 08 March 2016
# License: GPLV3
# Website: https://github.com/trizen
# Converts a stream of newline separated json data to csv format.
# See also: https://github.com/jehiah/json2csv
use 5.010;
use strict;
use warnings;
use Text::CSV qw();
use JSON qw(from_json);
use Getopt::Std qw(getopts);
use Encode qw(decode_utf8);
use Text::ParseWords qw(quotewords);
use open IO => ':encoding(UTF-8)', ':std';
@ARGV = map { decode_utf8($_) } @ARGV;
my %opt;
getopts('k:i:o:p:d:', \%opt);
my $in = \*ARGV;
my $out = \*STDOUT;
if (defined($opt{i})) {
open $in, '<', $opt{i}
or die "Can't open file `$opt{i}' for reading: $!";
}
if (defined($opt{o})) {
open $out, '>', $opt{o}
or die "Can't open file `$opt{o}' for writing: $!";
}
sub usage {
my ($code) = @_;
print <<"EOT";
usage: $0 [options] [< input.json] [> output.csv]
options:
-k fields.0,and,nested.fields,to,output
-i /path/to/input.json (optional; default is stdin)
-o /path/to/output.csv (optional; default is stdout)
-d delimiter separator for csv (default: ",")
-p print csv header row
example:
$0 -k user.name,list.0,remote_ip -i input.json -o output.csv
EOT
exit($code);
}
$opt{k} // usage(1);
sub unescape {
my ($str) = @_;
my %esc = (
a => "\a",
t => "\t",
r => "\r",
n => "\n",
e => "\e",
b => "\b",
f => "\f",
);
$str =~ s{(?<!\\)(?:\\\\)*\\([@{[keys %esc]}])}{$esc{$1}}g;
$str;
}
my @fields = map { [quotewords(qr/\./, 0, $_)] } quotewords(qr/\s*,\s*/, 1, $opt{k});
say($opt{p}) if defined($opt{p});
my $csv = Text::CSV->new(
{
eol => "\n",
sep_char => defined($opt{d}) ? unescape($opt{d}) : ",",
}
)
or die "Cannot use CSV: " . Text::CSV->error_diag();
sub extract {
my ($json, $fields) = @_;
my @row;
foreach my $field (@{$fields}) {
my $ref = $json;
foreach my $key (@{$field}) {
if ( ref($ref) eq 'ARRAY'
and $key =~ /^[-+]?[0-9]+\z/
and exists($ref->[$key])) {
$ref = $ref->[$key];
}
elsif (ref($ref) eq 'HASH'
and exists($ref->{$key})) {
$ref = $ref->{$key};
}
else {
local $" = ' -> ';
warn "[!] Field `$key' (from `@{$field}') does not exists in JSON.\n";
$ref = undef;
last;
}
}
push @row, $ref;
}
\@row;
}
while (defined(my $line = <$in>)) {
my $data = extract(from_json($line), \@fields);
$csv->print($out, $data);
}