Alexander Farber
Alexander Farber

Reputation: 22998

How to avoid putting quotes around numbers with Perl JSON module?

Is there please a way to make the encode_json method NOT to put quotes around numbers?

For example I'm using perl 5.10 on CentOS 6.3 (and also ActiveState perl 5.16 on Win 7) and it puts the quotes where it shouldn't:

# perl -MJSON -e 'print encode_json({a => [split(",", "1.2,30")]});'
{"a":["1.2","30"]}

That is, ok yes, it sees the "1.2" and "30" as strings in the code above, but my problem is:

My perl script parses CSV-files and generate HTML-files using Google Charts and the latter are confused by having quotes around number-values (eventhough I told them that the column is of type "numeric").

As a workaround I iterate through my data stracture and replace every number by sprintf "%f", $val but this results in too many nulls being displayed after each number, which makes the charts correct, but ugly looking:

# perl -e 'printf "%f", "30"'
30.000000

enter image description here

UPDATE:

Yes, adding a zero or multiplying by one seems to work at the first glance:

# perl -MJSON -e 'print encode_json({a => [map {1 * $_} split(",", "1.2,30")]});'
{"a":[1.2,30]}

but in my real script it still doesn't work for the floating numbers.

And you can see the problem I still have at the CLI when using Dumper module too:

# perl -MData::Dumper -e 'print Dumper({a => [map {1.0 * $_} split(",", "1.2,30")]});'
$VAR1 = {
          'a' => [
                   '1.2', # <= THIS IS MY PROBLEM AND CONFUSES GOOGLE CHARTS
                   30
                 ]
        };

Upvotes: 4

Views: 8700

Answers (6)

Hln
Hln

Reputation: 767

I have a similar case. My perl code generates (in a complicated way) a complicated data structure, then serializes it with JSON::to_json and passes it to javascript. There're a lot of numbers on different depth in the data structure, and javascript will do arithmetic on them.

'+' in javascript works as addition for numbers and as concatenation for strings, so it's critically important not to put numbers in quotes when converting them to json. On the other hand, the data is quite complex, so I need simple and universal way to force numbers be numbers in arbitrary array/hash/array of hashes etc.

So, I ended up with function like this:

use Scalar::Util qw(looks_like_number);
sub force_numbers
{  
    if (ref $_[0] eq ""){
        if ( looks_like_number($_[0]) ){
            $_[0] += 0;
        }   
    } elsif ( ref $_[0] eq 'ARRAY' ){
        force_numbers($_) for @{$_[0]};
    } elsif ( ref $_[0] eq 'HASH' ) {
        force_numbers($_) for values %{$_[0]};
    }   

    return $_[0];
}   

Now I can apply it before converting data to json:

print to_json(force_numbers($data));

Upvotes: 1

simonp
simonp

Reputation: 2867

Your problem is that although you're correctly converting it to a number, it's getting converted back to a string before you call encode_json. This is because you're calling Data::Dumper on it in a debug statement. If you comment out your call to Data::Dumper, you'll find encode_json outputs the right thing.

e.g. this example shows the JSON object before and after calling Dumper on the object:

$ perl -MData::Dumper -MJSON -e '
my $obj = {a => [map { $_ - 0 } split(",", "1.2,30")]};
print "JSON before: ",encode_json($obj),"\n";
print "Dumper: ",Dumper($obj);
print "JSON after: ",encode_json($obj),"\n";
'
JSON before: {"a":[1.2,30]}
Dumper: $VAR1 = {
          'a' => [
                   '1.2',
                   30
                 ]
        };
JSON after: {"a":["1.2",30]}

as you can see, Dumper actually modifies the object you're dumping, affecting your subsequent encode_json call.

Upvotes: 14

emazep
emazep

Reputation: 479

The correct solution has already been given.

If for any reason you find it difficult to adopt, here is an even easier suggestion, thanks to Data::Dump which does all the hard work for us:

perl -MJSON -MData::Dump=pp -le 'print encode_json eval pp { a => [split /,/, "1.2,30,b"] }'

which gives:

{"a":[1.2,30,"b"]}

If your efficiency constraints permit to afford the pp/eval roundtrip, it should solve your problem almost transparently.

It also spots that the quoted reals you obtained in your tests were due only to a Data::Dumper (questionable) choice, as already said by others.

Upvotes: 0

gpojd
gpojd

Reputation: 23075

You already have your answer for this, but I wanted to point out the idiomatic way to do this in Perl. From chapter 10 of Modern Perl:

To ensure that Perl treats a value as numeric, add zero:

my $numeric_value = 0 + $value;

To ensure that Perl treats a value as boolean, double negate it:

my $boolean_value = !! $value;

To ensure that Perl treats a value as a string, concatenate it with the empty string:

my $string_value = '' . $value;

Upvotes: 12

Joni
Joni

Reputation: 111329

Perl keeps track of scalar datatypes internally. You can force the type of the scalar into a number by using it in an arithmetic expression. For example:

my $scalar = "3.14"; # $scalar is a string
$scalar *= 1;        # Now $scalar is a number

On the other hand, if your problem is having too many zeroes in the output of sprintf, you can fix that by changing the precision to something else, for example 2 digits:

sprintf "%.2f", $val

Upvotes: 4

Quentin
Quentin

Reputation: 943769

They are coming out as strings because you get strings out of split.

There might be a nicer way to do it, but multiplying them by 1 appears to work:

perl -MJSON -e 'print encode_json({a => [map { $_ * 1  } split(",", "1.2,30")]});'

Upvotes: 6

Related Questions