Håkon Hægland
Håkon Hægland

Reputation: 40718

Format JSON output of arrays

I would like JSON:XS to output array elements on a single line. For example:

use warnings;
use strict;
use JSON::XS;

my $h={a=>[1,2,3,4],b=>3};
my $coder = JSON::XS->new->pretty;
my $txt   = $coder->encode($h);
print $txt;

This gives output:

{
   "b" : 3,
   "a" : [
      1,
      2,
      3,
      4
   ]
}

whereas my desired output is:

{
   "b" : 3,
   "a" : [ 1, 2, 3, 4 ]
}

Upvotes: 3

Views: 267

Answers (2)

afenster
afenster

Reputation: 3608

This behavior is hardcoded in the module. If you are ok with patching the module code on your computer it can be easily done (the following instructions are for Linux CPAN):

  1. Go to CPAN build directory /root/.cpan/build/JSON-XS-3.01-* (the actual name has some random characters at the end)
  2. Apply the following patch to XS.xs:
--- XS.xs.orig  2014-11-08 14:22:37.682348401 +0300
+++ XS.xs       2014-11-08 14:30:01.447643990 +0300
@@ -486,6 +486,15 @@
     encode_space (enc);
 }

+INLINE void
+encode_comma_singleline (enc_t *enc)
+{
+  encode_ch (enc, ',');
+
+  if (enc->json.flags & F_SPACE_AFTER)
+    encode_space (enc);
+}
+
 static void encode_sv (enc_t *enc, SV *sv);

 static void
@@ -500,24 +509,18 @@

   if (len >= 0)
     {
-      encode_nl (enc); ++enc->indent;
-
       for (i = 0; i <= len; ++i)
         {
           SV **svp = av_fetch (av, i, 0);

-          encode_indent (enc);
-
           if (svp)
             encode_sv (enc, *svp);
           else
             encode_str (enc, "null", 4, 0);

           if (i < len)
-            encode_comma (enc);
+            encode_comma_singleline (enc);
         }
-
-      encode_nl (enc); --enc->indent; encode_indent (enc);
     }

   encode_ch (enc, ']');
  1. Run make, then make install.

  2. Check your script:

$ perl json.pl
{
   "a" : [1, 2, 3, 4],
   "b" : 3
}

Some required disclaimer: accept changing the module locally at your own risk, the correct way of course is to make a good patch which would accept the corresponding configuration option, and submit this patch to the author of the module. But if you need it to run on your computer only, that will perfectly work.

Upvotes: 2

H&#229;kon H&#230;gland
H&#229;kon H&#230;gland

Reputation: 40718

If arrays cannot contain hashes, the following could be workaround:

use warnings;
use strict;
use JSON::XS;
use Text::Balanced qw(extract_bracketed extract_delimited);
use Text::CSV;
my $csv = Text::CSV->new( { sep_char => ',', allow_whitespace => 1 } );

my $h = { a => "[", g => [ "[", 2, "bb]]", 4 ], b => 3, c => [ 1, 2, 3, 4 ] };
my $coder = JSON::XS->new->pretty;
my $txt   = $coder->encode($h);
my $str = "";
while (1) {
    my $ind1 = index( $txt, '"' );
    my $ind2 = index( $txt, '[' );
    if ( $ind1 >= 0 && $ind2 >= 0 ) {
        if ( $ind1 < $ind2 ) {
            skipQuoted( \$txt, \$str );
            next;
        }
    }
    elsif ( $ind2 < 0 ) {
        $str .= $txt;
        last;
    }
    my ( $etxt, $end, $beg ) = extract_bracketed( $txt, '["]', '[^[]*' );
    die "Unexpected!" if !defined $etxt;
    $str .= $beg;
    $etxt = substr( $etxt, 1, length($etxt) - 2 )
      ;    #strip leading and trailing brackets
    $etxt =~ s{\n}{}g;
    my @elem;
    if ( $csv->parse($etxt) ) {
        @elem = $csv->fields();
    }
    else {
        die "Unexpected!";
    }
    $str .= '[ ' . processFields( \@elem ) . ' ]';
    $txt = $end;
}

print $str;

sub skipQuoted {
    my ( $txt, $str ) = @_;

    my ( $s1, $s2, $s3 ) = extract_delimited( $$txt, '"', '[^"]*' );
    die "Unexpected!" if !defined $s1;
    $$str .= $s3 . $s1;
    $$txt = $s2;
}

sub processFields {
    my ($a) = @_;

    for (@$a) {
        if ( $_ !~ /^-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?$/ ) {
            $_ = '"' . $_ . '"';
        }
    }
    return join( ", ", @$a );
}

Output:

{
   "a" : "[",
   "g" : [ "[", 2, "bb]]", 4 ],
   "b" : 3,
   "c" : [ 1, 2, 3, 4 ]
}

Upvotes: 0

Related Questions