death to JSON::PP::Boolean

Dear Lazyweb, how do I get either Data::Dumper or Data::Printer to print JSON booleans sanely in my debug output?

    use Data::Dumper;
    use Data::Printer;
    use JSON::Any;

    my $foo = JSON::Any->new->jsonToObj('{"a":true, "b":false, "c":true}');
    print Dumper ($foo), "\n";
    print np ($foo), "\n";

That prints this unreadable garbage:

    $VAR1 = {
              'b' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
              'a' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
              'c' => $VAR1->{'a'}
            };

    \ {
        a  JSON::PP::Boolean  {
            public methods (0)
            private methods (1) : __ANON__
            internals: 1
        },
        b  JSON::PP::Boolean  {
            public methods (0)
            private methods (1) : __ANON__
            internals: 0
        },
        c  var{a}
    }

When I just want to see:

    {
      'a' => 1,
      'b' => 0,
      'c' => 1
    };

None of this works:

    $JSON::PP::true = 1;
    $JSON::PP::false = 0;

    sub JSON::PP::true { return 1 };
    sub JSON::PP::false { return 0 };

    $JSON::Any::true = 1;
    $JSON::Any::false = 0;

    sub JSON::Any::true { return 1 };
    sub JSON::Any::false { return 0 };

This almost works:

    sub JSON::PP::Boolean::_data_printer {
      return ($_[0] eq JSON::PP::true ? "1" : "0") }

But this is still stupid:

    \ {
        a  0,
        b  1,
        c  var{a}
    }
Tags: , ,

22 Responses:

  1. Matt S Trout says:

    $Data::Dumper::Freezer will let you set a method for serializing it.

    $Data::Dumper::Deepcopy will make it not bother with the $VAR1->{a} thing. Not sure what the equivalent of that is for Data::Printer.

    • jwz says:

      Freezer seems to allow you to side-effect an object's internals before it is printed, but not change how it is printed.

      Toaster seems to just append the string "->toaster_method_name()" to the printed output.

      • Matt S Trout says:

        sub Freezer { $_[0] = 'fuck off' }

        Data::Dumper is ancient perl, so I think you have to mangle yourself.

        • jwz says:

          $Data::Dumper::Freezer = '_debool';
          sub JSON::PP::Boolean::_debool { $_[0] = ($_[0] eq JSON::PP::true ? "1" : "0"); }
          print Dumper (JSON::Any->new->jsonToObj('{"a":true, "b":false, "c":true}'));

          ==>

          $VAR1 = {
            'c' => \undef,
            'b' => \undef,
            'a' => \undef
          };

          And this makes Perl segfault, whee:

          sub JSON::PP::Boolean::_debool { $_[0] = ($_[0] eq JSON::PP::true ? 1 : 0); }

          • It expects that the thing it passed you as $_[0] to still be a reference afterwards. You can speak a prayer and do this:

            print do {
                local $Data::Dumper::Freezer = '_debool';
                local *JSON::PP::Boolean::_debool
                    = sub { $_[0] = \\\\\\\\\\\(0+$_[0]) if JSON::PP::is_bool $_[0] };
                Dumper($foo) =~ s!\\\\\\\\\\\\\\\\\\\\\\!!gr;
            };

            But that may not be an option because it will modify the data structure in order to print it.

            I can’t be bothered to look into Data::Printer.

            I know two less horrible approaches that will work, though I don’t know if they’re applicable for you.

            If you don’t need to preserve the fact that these were JSON booleans and you have control of the place where the JSON gets decoded, just change the JSON::PP true/false values before decoding:

            my $foo = do {
                local $JSON::PP::true = !!1, local $JSON::PP::false = !!0;
                JSON::PP->new->decode('{"a":true, "b":false, "c":true}');
            };

            Then the data structure won’t have those objects in the first place and it will print sanely regardless of dumper with no further faffing around.

            (And yes, perl’s canonical false value prints as an empty string rather than a numeric 0. Use 1 and 0 instead of !!1 and !!0 if you hate that.)

            If that doesn’t work for you, but you can switch to a different dumper, use Data::Dump, which has a very simple/obvious/nice interface for what you want:

            use Data::Dump::Filtered 'dump_filtered';
            dump_filtered( $foo, sub { { dump => "!!$_[1]" } if JSON::PP::is_bool $_[1] } );

            If you can’t do that either, then I guess the question would be if you can at least afford to copy the data structure to dump it. If so, you could use Clone and do that backslash train hack. Or maybe walk the data structure and twiddle the values into real true/false (or 1/0) values. Maybe with use Data::Visitor::Callback or something.

            If you can’t do even that… well then I’m out of ideas.

            P.S.: Using JSON::Any but only handling JSON::PP booleans is fragile if this isn’t just for printf debugger sessions.

            P.P.S.: Your comment system sucks at code formatting. :-)

  2. Andy says:

    print JSON::Any->new(pretty=>true)->objToJson($foo);

    • jwz says:

      That changes nothing. What do you expect it to do?

    • jwz says:

      Oh wait, nevermind, I see what you did.

      Well, "convert Perl objects back to JSON and pretty-print those instead" is not quite what I was hoping for, because it's actually the Perl objects I'm interested in.

      • Kevin Geiss says:

        the place I work at has many many (many) old modperl webservices we still maintain to this day, and we convert the perl objects to json (we use JSON::XS because that's what we had back in the day) and then print them (without prettifying them). looks nice in the logs without all the extra junk.

  3. Tom says:

    If I explicitly use JSON::PP instead of JSON::Any or JSON, one of the tricks you posted seems to work (with perl-5.20.2):


    #!/usr/bin/perl

    use strict;
    use warnings;
    use diagnostics;
    use feature 'say';
    use Data::Dumper;
    use JSON::PP;

    $JSON::PP::true=1;
    $JSON::PP::false=0;

    my $json = JSON::PP->new->allow_nonref;
    my $foo = $json->decode('{"a":true, "b":false, "c":true}');
    print Dumper ($foo), "n";

    outputs:

    $VAR1 = {
    'b' => 0,
    'c' => 1,
    'a' => 1
    };

  4. Bai Hui says:

    Stop using this shit and migrate to Python.

  5. kc says:

    The var{a} reference can be overcome by setting _reftype in your printer method:


    use Data::Printer;
    use JSON::Any;

    sub JSON::PP::Boolean::_data_printer {
      my ($item, $p) = @_;
      $p->{_reftype} = 1;
      return ($item eq JSON::PP::true) ? "1" : "0" }

    print np(JSON::Any->new->jsonToObj('{"a":true, "b":false, "c":true}'));

    • Ben says:

      That's really good to know, thanks!

      I've run into similar problems to jwz's original question, and I never found a better way than using '$Data::Dumper::Deepcopy = 1' to improve Data::Dumper's output. I've gone as far as writing my own DataDumper equivalent to try and make the output better for many data structures, but didn't realise Data::Printer even existed. It looks like I can use Data::Printer and add my own methods for rendering specific classes that I want to improve, instead of re-inventing everything. Thanks!

  6. bp says:

    This makes me really sad, but then again it's Perl. Untested code, but you get the idea.

    sub true {
    return 1 if $caller[0] =~ /^Data::/;
    return JSON::PP::Boolean::true;
    }

  7. Sam Kington says:

    You could dump a copy of the structure that didn't have those booleans in it, if the dumping code you're using doesn't do exactly what you wanted? This worked on your toy data structure:

    use JSON::Any;
    use Data::Dumper;
    use Data::Visitor::Callback;

    my $foo = JSON::Any->new->jsonToObj('{"a":true, "b":false, "c":true}');
    my $squash_bool = sub { $_ ? 1 : 0 };
    my $visitor = Data::Visitor::Callback->new(
    'JSON::PP::Boolean' => $squash_bool,
    'JSON::XS::Boolean' => $squash_bool,
    seen => $squash_bool,
    );
    my $munged = $visitor->visit($foo);
    print Dumper ($munged), "\n";

  8. Sam Kington says:

    Hmmm, could have sworn that I posted this earlier. Maybe I was trying to be too clever in the markup.

    Anyway, this works for your toy example: rather than fight the dumper, mung (a copy of) the data structure. It works with various flavours of JSON back-ends as well.

    use JSON::Any;
    use Data::Dumper;
    use Data::Visitor::Callback;

    my $foo = JSON::Any->new->jsonToObj('{"a":true, "b":false, "c":true}');
    my $squash_bool = sub { $_ ? 1 : 0 };
    my $visitor = Data::Visitor::Callback->new(
    'JSON::PP::Boolean' => $squash_bool,
    'JSON::XS::Boolean' => $squash_bool,
    seen => $squash_bool,
    );
    my $munged = $visitor->visit($foo);
    print Dumper ($munged), "\n";

    • Sam Kington says:

      Ack, duplicate - sorry! Was the comment held for moderation or something? Either way, please delete the dupe.

  9. Alex R says:

    Getting a bit closer but still doesn't deal with re-used variables:

    use Data::Printer filters=>{'JSON::PP::Boolean' => sub {int($_[0])}}
    use JSON::Any
    my $foo = JSON::Any->new->jsonToObj('{"a":true,"b":false,"c":true,"d":"hello"}')
    p($foo)
    ====>
    {
    a 1,
    b 0,
    c var{a},
    d "hello"
    }

  10. AFresh1 says:

    Late to the party, but didn't see any answers that actually claimed to work so figured I'd better come up with a terrible hack. I couldn't figure out any way to override what Data::Dumper::Dumpxs does, so instead you have to use Data::Dumper::Dumpperl which does all you to override the _dump method and therefore have access to the $val before Data::Dumper gets it.

    The following does print:

    $VAR1 = {
    'a' => 1,
    'b' => 0,
    'c' => 1
    };

    In all it's terribleness:

    #!/usr/bin/perl
    use strict;
    use warnings;
    use JSON::PP;

    print JSON::Dumper->Dumpperl(
    [ JSON::PP->new->decode('{"a": true, "b": false, "c": true }') ] );

    package JSON::Dumper;
    use parent 'Data::Dumper';

    sub _dump {
    my ($s, $val, $name) = @_;

    if ( ( ref $val || '' ) eq 'JSON::PP::Boolean' ) {
    $val = $val ? 1 : 0;
    }

    $s->SUPER::_dump($val, $name);
    }