Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Converting boolean values in XMLout

by this_is_a_test (Initiate)
on Jun 22, 2009 at 18:08 UTC ( #773713=perlquestion: print w/ replies, xml ) Need Help??
this_is_a_test has asked for the wisdom of the Perl Monks concerning the following question:

My json has boolean values and, when I construct a perl scalar and construct an XML record, it throws an error. Is there a way to convert the boolean values in a data structure to XML?
my $text = '{"a":"x","b":true}'; my $result = decode_json($text); my $rec = XMLout( $result, RootName => 'root', SuppressEmpty => 1) +;
In the above code, I get the foll. error - "Can't encode a value of type: JSON::XS::Boolean" ie. How do I deal with boolean values in the json. A print Dump on "$result" variable gives:
$result = { 'a' => 'x', 'b' => bless( do{\(my $o = 1)}, 'JSON::XS::Boolean' ) };
Thanks in advance.

Comment on Converting boolean values in XMLout
Select or Download Code
Re: Converting boolean values in XMLout
by ikegami (Pope) on Jun 22, 2009 at 18:32 UTC
    decode_json returns an object to represent the boolean values it finds. You'll need to walk the structure returned by decode_json to replace values for which JSON::XS::is_bool($node) is true with an appropriate value (such as 0+$node or $node?'true':'false').
      use strict; use warnings; use JSON::XS qw( decode_json ); use Scalar::Util qw( blessed reftype ); use XML::Simple qw( XMLout ); sub json_tree_to_xml_tree { my @to_visit = \( $_[0] ); while (@to_visit) { for my $node (${ pop @to_visit }) { if (!defined($node)) { die("Can't handle undef\n"); } elsif (JSON::XS::is_bool($node)) { # true => 1 # false => 0 $node += 0; } elsif (blessed($node)) { die("Can't handle objects\n"); } elsif (reftype($node)) { if (ref($node) eq 'ARRAY') { push @to_visit, map \$_, @$node; } elsif (ref($node) eq 'HASH') { push @to_visit, map \$_, values %$node; } else { die("Unexpected reference type\n"); } } } } } my $text = '{"a":"x","b":true}'; my $tree = decode_json($text); json_tree_to_xml_tree($tree); print XMLout($tree);

      It (at least) appears a bit simpler with recursion, although it's probably a bit slower.

      use strict; use warnings; use JSON::XS qw( decode_json ); use Scalar::Util qw( blessed reftype ); use XML::Simple qw( XMLout ); sub json_tree_to_xml_tree { for my $node (@_) { if (!defined($node)) { die("Can't handle undef\n"); } elsif (JSON::XS::is_bool($node)) { # true => 1 # false => 0 $node += 0; } elsif (blessed($node)) { die("Can't handle objects\n"); } elsif (reftype($node)) { if (ref($node) eq 'ARRAY') { json_tree_to_xml_tree( @$node ); } elsif (ref($node) eq 'HASH') { json_tree_to_xml_tree( values(%$node) ); } else { die("Unexpected reference type\n"); } } } } my $text = '{"a":"x","b":true}'; my $tree = decode_json($text); json_tree_to_xml_tree($tree); print XMLout($tree);
      You'll need to walk the structure

      For which there is the fine Data::Rmap module:

      use strict; use warnings; use JSON; use XML::Simple; use Data::Rmap qw(rmap_ref); use Data::Dumper; my $text = '{"a":"x","b":true}'; my $result = decode_json($text); print Dumper($result); rmap_ref { $_ = "$_" if JSON::is_bool($_) } $result; # or numify, like + ikegami shows above print Dumper($result); my $rec = XMLout( $result, RootName => 'root', SuppressEmpty => 1); print Dumper($rec);

      Output:

      $VAR1 = { 'a' => 'x', 'b' => bless( do{\(my $o = 1)}, 'JSON::XS::Boolean' ) }; $VAR1 = { 'a' => 'x', 'b' => 'true' }; $VAR1 = '<root a="x" b="true" /> ';

      In case it's useful to someone out there, I found that Rmap as used in pKai's sample code, didn't recurse as I wanted it to, for some Json strings anyway.

      For instance, if you have a nested json object where some of the values in an object are the same (e.g. a list of keys with boolean values), decode_json uses JSON::true or JSON::false for the first instance it comes across, but then adds hash references to an existing true or false for all subsequent instances. This is presumably for performance? Whatever the reason, it caused me a bit of chin-scratching (not hard!).

      Consider the following string of Json, (which might have been received as a post parameter, say):

      { "a": "x", "b": { "c":true, "d":false, "e":true, "f":false } }

      If I tried this in pKai's code above, you don't get the required result - XML::Simple still complains:

      $VAR1 = { 'a' => 'x', 'b' => { 'e' => bless( do{\(my $o = 1)}, 'JSON::XS::Boolean' + ), 'c' => $VAR1->{'b'}{'e'}, 'd' => bless( do{\(my $o = 0)}, 'JSON::XS::Boolean' + ), 'f' => $VAR1->{'b'}{'d'} } }; $VAR1 = { 'a' => 'x', 'b' => { 'e' => 'true', 'c' => bless( do{\(my $o = 1)}, 'JSON::XS::Boolean' + ), 'd' => 'false', 'f' => bless( do{\(my $o = 0)}, 'JSON::XS::Boolean' + ) } }; Can't encode a value of type: JSON::XS::Boolean at test2.pl line 13

      The Rmap docs state that if there are multiple routes to the same node, only the first will be followed... so I'm pretty sure this is working as designed, but it's just not what I needed...

      One way round it was just to repeat the same rmap_ref call against the object until there weren't any blessed objects left, but that seems dumb - it can't be very efficient? But it does work:

      use strict; use warnings; use JSON; use XML::Simple; use Data::Rmap qw(rmap_ref); use Data::Dumper; my $text = '{"a":"x","b":{"c":true,"d":false,"e":true,"f":false}}'; my $result = decode_json($text); print Dumper($result); rmap_ref { $_ = "$_" if JSON::is_bool($_) } $result; print Dumper($result); rmap_ref { $_ = "$_" if JSON::is_bool($_) } $result; print Dumper($result); my $rec = XMLout( $result, RootName => 'root', SuppressEmpty => 1); print Dumper($rec);
      $VAR1 = { 'a' => 'x', 'b' => { 'e' => bless( do{\(my $o = 1)}, 'JSON::XS::Boolean' + ), 'c' => $VAR1->{'b'}{'e'}, 'd' => bless( do{\(my $o = 0)}, 'JSON::XS::Boolean' + ), 'f' => $VAR1->{'b'}{'d'} } }; $VAR1 = { 'a' => 'x', 'b' => { 'e' => 'true', 'c' => bless( do{\(my $o = 1)}, 'JSON::XS::Boolean' + ), 'd' => 'false', 'f' => bless( do{\(my $o = 0)}, 'JSON::XS::Boolean' + ) } }; $VAR1 = { 'a' => 'x', 'b' => { 'e' => 'true', 'c' => 'true', 'd' => 'false', 'f' => 'false' } }; $VAR1 = '<root a="x"> <b c="true" d="false" e="true" f="false" /> </root> ';

      In the end, I used something similar to ikegami's code above to make sure I walk a nested structure fully, but instead of using 1 or 0 to substitute the blessed JSON booleans, I used references, so that I could 'round trip' the resulting object using encode_json and get proper json booleans at the client (rather than the integers 1 or 0).

      elsif (JSON::XS::is_bool($node)) { #$node += 0; # this results in 1 or 0 #$node = "$node"; # this results in "true" or "false" $node = ($node) ? \1 : \0; # this results in \1 or \0 (whic +h are mapped to Json true and false by encode_json...) }

      There's probably a neat way to use Rmap to do the walking, but it was beyond my abilities I'm afraid. :(

        decode_json uses JSON::true or JSON::false for the first instance it comes across, but then adds hash references to an existing true or false for all subsequent instances.

        No, not quite.

        When JSON::XS is loaded, it creates two objects

        $JSON::XS::true = do { bless \(my $dummy = 1), "JSON::XS::Boolean" }; $JSON::XS::false = do { bless \(my $dummy = 0), "JSON::XS::Boolean" };

        No other instances are ever created. No references to those are created. Those two objects are used everywhere.

        There's probably a neat way to use Rmap to do the walking, but it was beyond my abilities I'm afraid. :(

        It's refusing to visit the same reference twice. You could probably mess with its seen method (which appears to be a way at peeking at the module's internals).

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://773713]
Approved by ikegami
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (12)
As of 2014-08-20 17:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The best computer themed movie is:











    Results (120 votes), past polls