jsonfmt: Add pretty option
That completes the json_format() function for now. At least, it now does everything I had planned for it. Ended up at a bit over 300 LOC. That's larger than I had expected, but still alright.
This commit is contained in:
parent
1a0fb03205
commit
aebe5a93dc
3 changed files with 56 additions and 8 deletions
15
FU/Util.pod
15
FU/Util.pod
|
|
@ -51,13 +51,20 @@ The following C<%options> are supported:
|
||||||
|
|
||||||
=item canonical
|
=item canonical
|
||||||
|
|
||||||
When set to a true value, write hash keys in deterministic (sorted) order. This
|
Boolean, write hash keys in deterministic (sorted) order. This option currently
|
||||||
option currently has no effect on tied hashes.
|
has no effect on tied hashes.
|
||||||
|
|
||||||
|
=item pretty
|
||||||
|
|
||||||
|
Boolean, format JSON with newlines and indentation for easier reading. Beauty
|
||||||
|
is in the eye of the beholder, this option currently follows the convention
|
||||||
|
used by L<JSON::XS> and others: 3 space indent and one space around the C<:>
|
||||||
|
separating object keys and values. The exact format might change in later
|
||||||
|
versions.
|
||||||
|
|
||||||
=item utf8
|
=item utf8
|
||||||
|
|
||||||
When set to a true value, returns a UTF-8 encoded byte string instead of a Perl
|
Boolean, returns a UTF-8 encoded byte string instead of a Perl Unicode string.
|
||||||
Unicode string.
|
|
||||||
|
|
||||||
=item max_size
|
=item max_size
|
||||||
|
|
||||||
|
|
|
||||||
25
c/jsonfmt.c
25
c/jsonfmt.c
|
|
@ -2,10 +2,19 @@ typedef struct {
|
||||||
fustr out;
|
fustr out;
|
||||||
UV depth;
|
UV depth;
|
||||||
int canon;
|
int canon;
|
||||||
|
int pretty; /* <0 when disabled, current nesting level otherwise */
|
||||||
} fujson_fmt_ctx;
|
} fujson_fmt_ctx;
|
||||||
|
|
||||||
static void fujson_fmt(pTHX_ fujson_fmt_ctx *, SV *);
|
static void fujson_fmt(pTHX_ fujson_fmt_ctx *, SV *);
|
||||||
|
|
||||||
|
static void fujson_fmt_indent(pTHX_ fujson_fmt_ctx *ctx) {
|
||||||
|
if (ctx->pretty >= 0) {
|
||||||
|
char *buf = fustr_write_buf(&ctx->out, 1 + ctx->pretty*3);
|
||||||
|
*buf = '\n';
|
||||||
|
memset(buf+1, ' ', ctx->pretty*3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void fujson_fmt_str(pTHX_ fujson_fmt_ctx *ctx, const char *stri, size_t len, int utf8) {
|
static void fujson_fmt_str(pTHX_ fujson_fmt_ctx *ctx, const char *stri, size_t len, int utf8) {
|
||||||
size_t off = 0, loff;
|
size_t off = 0, loff;
|
||||||
const unsigned char *str = (const unsigned char *)stri;
|
const unsigned char *str = (const unsigned char *)stri;
|
||||||
|
|
@ -118,12 +127,16 @@ static void fujson_fmt_int(pTHX_ fujson_fmt_ctx *ctx, SV *val) {
|
||||||
static void fujson_fmt_av(pTHX_ fujson_fmt_ctx *ctx, AV *av) {
|
static void fujson_fmt_av(pTHX_ fujson_fmt_ctx *ctx, AV *av) {
|
||||||
int i, len = av_count(av);
|
int i, len = av_count(av);
|
||||||
fustr_write(&ctx->out, "[", 1);
|
fustr_write(&ctx->out, "[", 1);
|
||||||
|
ctx->pretty++;
|
||||||
for (i=0; i<len; i++) {
|
for (i=0; i<len; i++) {
|
||||||
if (i) fustr_write(&ctx->out, ",", 1);
|
if (i) fustr_write(&ctx->out, ",", 1);
|
||||||
|
fujson_fmt_indent(aTHX_ ctx);
|
||||||
SV **sv = av_fetch(av, i, 0);
|
SV **sv = av_fetch(av, i, 0);
|
||||||
if (sv) fujson_fmt(aTHX_ ctx, *sv); /* sv will have magic if av is tied, but fujson_fmt() handles that. */
|
if (sv) fujson_fmt(aTHX_ ctx, *sv); /* sv will have magic if av is tied, but fujson_fmt() handles that. */
|
||||||
else fustr_write(&ctx->out, "null", 4);
|
else fustr_write(&ctx->out, "null", 4);
|
||||||
}
|
}
|
||||||
|
ctx->pretty--;
|
||||||
|
if (i) fujson_fmt_indent(aTHX_ ctx);
|
||||||
fustr_write(&ctx->out, "]", 1);
|
fustr_write(&ctx->out, "]", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,9 +161,11 @@ static int fujson_fmt_hvcmp(const void *pa, const void *pb) {
|
||||||
static void fujson_fmt_hvkv(pTHX_ fujson_fmt_ctx *ctx, HV *hv, HE *he, char **hestr) {
|
static void fujson_fmt_hvkv(pTHX_ fujson_fmt_ctx *ctx, HV *hv, HE *he, char **hestr) {
|
||||||
STRLEN helen;
|
STRLEN helen;
|
||||||
if (*hestr) fustr_write(&ctx->out, ",", 1);
|
if (*hestr) fustr_write(&ctx->out, ",", 1);
|
||||||
|
fujson_fmt_indent(aTHX_ ctx);
|
||||||
*hestr = HePV(he, helen);
|
*hestr = HePV(he, helen);
|
||||||
fujson_fmt_str(aTHX_ ctx, *hestr, helen, HeUTF8(he));
|
fujson_fmt_str(aTHX_ ctx, *hestr, helen, HeUTF8(he));
|
||||||
fustr_write(&ctx->out, ":", 1);
|
if (ctx->pretty > 0) fustr_write(&ctx->out, " : ", 3);
|
||||||
|
else fustr_write(&ctx->out, ":", 1);
|
||||||
fujson_fmt(aTHX_ ctx, UNLIKELY(SvMAGICAL(hv)) ? hv_iterval(hv, he) : HeVAL(he));
|
fujson_fmt(aTHX_ ctx, UNLIKELY(SvMAGICAL(hv)) ? hv_iterval(hv, he) : HeVAL(he));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,6 +175,7 @@ static void fujson_fmt_hv(pTHX_ fujson_fmt_ctx *ctx, HV *hv) {
|
||||||
|
|
||||||
int numkeys = hv_iterinit(hv);
|
int numkeys = hv_iterinit(hv);
|
||||||
fustr_write(&ctx->out, "{", 1);
|
fustr_write(&ctx->out, "{", 1);
|
||||||
|
ctx->pretty++;
|
||||||
|
|
||||||
/* Canonical order on tied hashes is not supported. Cpanel::JSON::XS has
|
/* Canonical order on tied hashes is not supported. Cpanel::JSON::XS has
|
||||||
* code to deal with that case and it's absolutely horrifying. */
|
* code to deal with that case and it's absolutely horrifying. */
|
||||||
|
|
@ -187,6 +203,8 @@ static void fujson_fmt_hv(pTHX_ fujson_fmt_ctx *ctx, HV *hv) {
|
||||||
} else {
|
} else {
|
||||||
while ((he = hv_iternext(hv))) fujson_fmt_hvkv(aTHX_ ctx, hv, he, &hestr);
|
while ((he = hv_iternext(hv))) fujson_fmt_hvkv(aTHX_ ctx, hv, he, &hestr);
|
||||||
}
|
}
|
||||||
|
ctx->pretty--;
|
||||||
|
if (hestr) fujson_fmt_indent(aTHX_ ctx);
|
||||||
fustr_write(&ctx->out, "}", 1);
|
fustr_write(&ctx->out, "}", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,6 +287,7 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
||||||
|
|
||||||
ctx.out.maxlen = 0;
|
ctx.out.maxlen = 0;
|
||||||
ctx.depth = 0;
|
ctx.depth = 0;
|
||||||
|
ctx.pretty = INT_MIN;
|
||||||
ctx.canon = 0;
|
ctx.canon = 0;
|
||||||
while (i < argc) {
|
while (i < argc) {
|
||||||
arg = SvPV_nolen(ST(i));
|
arg = SvPV_nolen(ST(i));
|
||||||
|
|
@ -278,6 +297,7 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
if (strcmp(arg, "canonical") == 0) ctx.canon = SvPVXtrue(r);
|
if (strcmp(arg, "canonical") == 0) ctx.canon = SvPVXtrue(r);
|
||||||
|
else if (strcmp(arg, "pretty") == 0) ctx.pretty = SvPVXtrue(r) ? 0 : INT_MIN;
|
||||||
else if (strcmp(arg, "utf8") == 0) encutf8 = SvPVXtrue(r);
|
else if (strcmp(arg, "utf8") == 0) encutf8 = SvPVXtrue(r);
|
||||||
else if (strcmp(arg, "max_size") == 0) ctx.out.maxlen = SvUV(r);
|
else if (strcmp(arg, "max_size") == 0) ctx.out.maxlen = SvUV(r);
|
||||||
else if (strcmp(arg, "max_depth") == 0) ctx.depth = SvUV(r);
|
else if (strcmp(arg, "max_depth") == 0) ctx.depth = SvUV(r);
|
||||||
|
|
@ -288,9 +308,8 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
||||||
|
|
||||||
fustr_init(&ctx.out, 128, ctx.out.maxlen);
|
fustr_init(&ctx.out, 128, ctx.out.maxlen);
|
||||||
fujson_fmt(aTHX_ &ctx, val);
|
fujson_fmt(aTHX_ &ctx, val);
|
||||||
|
if (ctx.pretty >= 0) fustr_write(&ctx.out, "\n", 1);
|
||||||
r = fustr_done(&ctx.out);
|
r = fustr_done(&ctx.out);
|
||||||
if (!encutf8) SvUTF8_on(r);
|
if (!encutf8) SvUTF8_on(r);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: pretty */
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ my @errors = (
|
||||||
do { my $o = {}; bless $o, 'MyToJSONSelf' }, qr/MyToJSONSelf::TO_JSON method returned same object as was passed instead of a new one/,
|
do { my $o = {}; bless $o, 'MyToJSONSelf' }, qr/MyToJSONSelf::TO_JSON method returned same object as was passed instead of a new one/,
|
||||||
);
|
);
|
||||||
|
|
||||||
plan tests => @tests*2 + @errors/2 + 9;
|
plan tests => @tests*2 + @errors/2 + 10;
|
||||||
|
|
||||||
for my($in, $exp) (@tests) {
|
for my($in, $exp) (@tests) {
|
||||||
my $out = json_format $in;
|
my $out = json_format $in;
|
||||||
|
|
@ -90,6 +90,28 @@ for my ($in, $exp) (@errors) {
|
||||||
is json_format({qw/a 1 b 2 c 3 d 4 d1 5 d11 6/, do { use utf8; qw/ü 7 月 8 💩 9/ }}, canonical => 1),
|
is json_format({qw/a 1 b 2 c 3 d 4 d1 5 d11 6/, do { use utf8; qw/ü 7 月 8 💩 9/ }}, canonical => 1),
|
||||||
do { use utf8; '{"a":"1","b":"2","c":"3","d":"4","d1":"5","d11":"6","ü":"7","月":"8","💩":"9"}' };
|
do { use utf8; '{"a":"1","b":"2","c":"3","d":"4","d1":"5","d11":"6","ü":"7","月":"8","💩":"9"}' };
|
||||||
|
|
||||||
|
is json_format(
|
||||||
|
{ a => [], b => {}, c => { x => 1 }, d => { y => true, z => false }, e => [1,2,3] },
|
||||||
|
canonical => 1, pretty => 1
|
||||||
|
), <<_;
|
||||||
|
{
|
||||||
|
"a" : [],
|
||||||
|
"b" : {},
|
||||||
|
"c" : {
|
||||||
|
"x" : 1
|
||||||
|
},
|
||||||
|
"d" : {
|
||||||
|
"y" : true,
|
||||||
|
"z" : false
|
||||||
|
},
|
||||||
|
"e" : [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_
|
||||||
|
|
||||||
|
|
||||||
eval { json_format [[]], max_depth => 2 };
|
eval { json_format [[]], max_depth => 2 };
|
||||||
like $@, qr/max_depth exceeded while formatting JSON/;
|
like $@, qr/max_depth exceeded while formatting JSON/;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue