http://www.perlmonks.org?node_id=1058198

sedusedan has asked for the wisdom of the Perl Monks concerning the following question:

For some reason (i.e. my validator code generator currently assuming bool data as "!ref($data)"), I'd prefer that JSON::{PP,XS} do not decode JSON true/false as JSON::{PP,XS}::Boolean objects, but as 1/0 instead. I don't care about being able to round-trip boolean data back to JSON at this point.

For JSON::PP, this is rather easy to do, just set $JSON::PP::true=1 and $JSON::PP::false=0 after loading the module. But this simple trick does not work for JSON::XS.

Currently I use Data::Clean::FromJSON to convert JSON::XS::Boolean objects to 1/0, but this adds overhead. It'd be nice if JSON::XS does not produce the JSON::XS::Boolean objects in the first place.

Replies are listed 'Best First'.
Re: Making JSON::{PP,XS} not decode true/false to JSON::{PP,XS}::Boolean objects
by davido (Cardinal) on Oct 14, 2013 at 16:54 UTC

    You could probably construct something inside of a filter_json_object callback. But off the top of my head it seems like you would need to recurse into whatever $_[0] holds, looking for JSON::Boolean objects and converting them.


    Dave

        Data::Rmap is also an alternative (and is faster in this particular case). But they are both rather slow, so I created Data::Clean::JSON and Data::Clean::FromJSON which generate Perl code to do the cleansing and can be several times faster. A benchmark (from slowest to fastest):
        % perl -MBench -MStorable=dclone -MData::Dump -MJSON -MData::Visitor:: +Callback -E' $data=JSON->new->decode(q([null, true, false, 1, 2, 3, "a", "b", [], { +}])); dd $data; $v=Data::Visitor::Callback->new("JSON::PP::Boolean"=>sub{$_=$_?1:0}, " +JSON::XS::Boolean"=>sub{$_=$_?1:0}); bench sub { $data2=dclone($data); $v->visit($data2) }, -1; dd $data2' [ undef, bless(do{\(my $o = 1)}, "JSON::XS::Boolean"), bless(do{\(my $o = 0)}, "JSON::XS::Boolean"), 1, 2, 3, "a", "b", [], {}, ] 4846 calls (3328/s), 1.456s (0.301ms/call) [undef, 1, 0 .. 3, "a", "b", [], {}] % perl -MBench -MStorable=dclone -MData::Dump -MJSON -MData::Rmap=rmap +_ref -E' $data=JSON->new->decode(q([null, true, false, 1, 2, 3, "a", "b", [], { +}])); dd $data; bench sub { $data2=clone($data); rmap_ref { $_ = ref($_) eq "JSON::PP: +:Boolean" || ref($_) eq "JSON::XS::Boolean" ? ($_?1:0) : $_ } $data } +, -1; dd $data2' [ undef, bless(do{\(my $o = 1)}, "JSON::XS::Boolean"), bless(do{\(my $o = 0)}, "JSON::XS::Boolean"), 1, 2, 3, "a", "b", [], {}, ] 13573 calls (11735/s), 1.157s (0.0852ms/call) [undef, 1, 0 .. 3, "a", "b", [], {}] % perl -MBench -MStorable=dclone -MData::Dump -MJSON -MData::Clean::Fr +omJSON -E' $data=JSON->new->decode(q([null, true, false, 1, 2, 3, "a", "b", [], { +}])); dd $data; $cleanser=Data::Clean::FromJSON->new; bench sub { $data2=dclone($data); $cleanser->clean_in_place($data2) }, + -1; dd $data2' [ undef, bless(do{\(my $o = 1)}, "JSON::XS::Boolean"), bless(do{\(my $o = 0)}, "JSON::XS::Boolean"), 1, 2, 3, "a", "b", [], {}, ] 33781 calls (32790/s), 1.030s (0.0305ms/call) [undef, 1, 0 .. 3, "a", "b", [], {}]
      I'm afraid not, filter_json_object will only be called for JSON object (hash). There's also filter_json_single_key_object which is even more selective (called for JSON objects having a single key with a specific name). There's no filter mechanism in JSON that calls a callback for a boolean value.
Re: Making JSON::{PP,XS} not decode true/false to JSON::{PP,XS}::Boolean objects (PL_sv_yes PL_sv_no !!1 !!0 )
by Anonymous Monk on Oct 15, 2013 at 01:49 UTC

    Edit the XS version to use &PL_sv_yes and & PL_sv_no

    Edit the PP version to use !!1 and !!0 respectively

      Yes of course :) I was hoping that there would be a way to do it without going to the source.
        I was hoping that there would be a way to do it without going to the source.

        How were you going to figure this out without going to the source?

        Going to the source, I saw that it is possible to change what JSON::XS returns for 'true' and 'false', if you get your stuff run at the right time. For example:

        use XSLoader; BEGIN { my $load = \&XSLoader::load; sub load { *JSON::XS::true = *true; *JSON::XS::false = *false; goto &$load; } *XSLoader::load = *load; our( $true, $false ) = \( 1, 0 ); sub true() { $true } sub false() { $false } } use JSON::XS(); print ref($_), ' ', $_, $/ for @{ JSON::XS->new()->decode('["0","1",true,false]') };

        But JSON::XS blithely assumes that $JSON::XS::true (and ...false) will contain references. If you adjust my code above so that they don't contain references, then as soon as you try to decode JSON containing 'true' or 'false', you'll get a core dump.

        If I were you, I'd just use something much simpler like JSON::Tiny. And I'd file a patch request to have "my $TRUE" changed to "our $TRUE", etc.

        - tye