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
|
||||
|
||||
When set to a true value, write hash keys in deterministic (sorted) order. This
|
||||
option currently has no effect on tied hashes.
|
||||
Boolean, write hash keys in deterministic (sorted) order. This option currently
|
||||
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
|
||||
|
||||
When set to a true value, returns a UTF-8 encoded byte string instead of a Perl
|
||||
Unicode string.
|
||||
Boolean, returns a UTF-8 encoded byte string instead of a Perl Unicode string.
|
||||
|
||||
=item max_size
|
||||
|
||||
|
|
|
|||
25
c/jsonfmt.c
25
c/jsonfmt.c
|
|
@ -2,10 +2,19 @@ typedef struct {
|
|||
fustr out;
|
||||
UV depth;
|
||||
int canon;
|
||||
int pretty; /* <0 when disabled, current nesting level otherwise */
|
||||
} fujson_fmt_ctx;
|
||||
|
||||
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) {
|
||||
size_t off = 0, loff;
|
||||
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) {
|
||||
int i, len = av_count(av);
|
||||
fustr_write(&ctx->out, "[", 1);
|
||||
ctx->pretty++;
|
||||
for (i=0; i<len; i++) {
|
||||
if (i) fustr_write(&ctx->out, ",", 1);
|
||||
fujson_fmt_indent(aTHX_ ctx);
|
||||
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. */
|
||||
else fustr_write(&ctx->out, "null", 4);
|
||||
}
|
||||
ctx->pretty--;
|
||||
if (i) fujson_fmt_indent(aTHX_ ctx);
|
||||
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) {
|
||||
STRLEN helen;
|
||||
if (*hestr) fustr_write(&ctx->out, ",", 1);
|
||||
fujson_fmt_indent(aTHX_ ctx);
|
||||
*hestr = HePV(he, helen);
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +175,7 @@ static void fujson_fmt_hv(pTHX_ fujson_fmt_ctx *ctx, HV *hv) {
|
|||
|
||||
int numkeys = hv_iterinit(hv);
|
||||
fustr_write(&ctx->out, "{", 1);
|
||||
ctx->pretty++;
|
||||
|
||||
/* Canonical order on tied hashes is not supported. Cpanel::JSON::XS has
|
||||
* 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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +287,7 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
|||
|
||||
ctx.out.maxlen = 0;
|
||||
ctx.depth = 0;
|
||||
ctx.pretty = INT_MIN;
|
||||
ctx.canon = 0;
|
||||
while (i < argc) {
|
||||
arg = SvPV_nolen(ST(i));
|
||||
|
|
@ -278,6 +297,7 @@ static SV *fujson_fmt_xs(pTHX_ I32 ax, I32 argc, SV *val) {
|
|||
i++;
|
||||
|
||||
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, "max_size") == 0) ctx.out.maxlen = 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);
|
||||
fujson_fmt(aTHX_ &ctx, val);
|
||||
if (ctx.pretty >= 0) fustr_write(&ctx.out, "\n", 1);
|
||||
r = fustr_done(&ctx.out);
|
||||
if (!encutf8) SvUTF8_on(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/,
|
||||
);
|
||||
|
||||
plan tests => @tests*2 + @errors/2 + 9;
|
||||
plan tests => @tests*2 + @errors/2 + 10;
|
||||
|
||||
for my($in, $exp) (@tests) {
|
||||
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),
|
||||
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 };
|
||||
like $@, qr/max_depth exceeded while formatting JSON/;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue