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

Hi All,

I was just refactoring some hand crafted JSON to use JSON's to_json method. However, I couldn't actually see a way to do it, as some of the values are functions. A simple example:

{ a: function() {return true;} }

After perusing the JSON POD I can't see a way of getting this output from a Perl data structure.

Replies are listed 'Best First'.
Re: Outputting JSON with function () {...} values
by choroba (Cardinal) on Oct 22, 2018 at 14:49 UTC
    That's because the string is not JSON, but a JavaScript code. JSON only supports numbers, strings, booleans, arrays, and "objects" (read "hashes"), and null.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Outputting JSON with function () {...} values (serializing JS function objects)
by LanX (Saint) on Oct 22, 2018 at 15:10 UTC
    Is this even legal JSON ? °

    What you are showing is the JavaScript equivalent of a coderef in Perl, but IMHO JSON should be language agnostic.

    If you really need to transport the JS code, maybe try serializing the function object on the JS side with .toString() or .toSource() to a string.

    update

    demo from my FF console:

    >> var coderef = function() {return true;} undefined >> typeof coderef "function" >> coderef instanceof Function true >> coderef.toString() "function() {return true;}" >> coderef.toSource() "(function() {return true;})" >> JSON= {a: coderef.toString() } {…} a: "function() {return true;}"

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

    update

    °) Nope!

    From WP: JSON#Unsupported native data types

    JavaScript syntax defines several native data types that are not included in the JSON standard: Map, Set, Date, Error, Regular Expression, Function, Promise, and undefined.

    These JavaScript data types must be represented by some other data format, with the programs on both ends agreeing on how to convert between the types.

    As of 2011, there are some de facto standards, e.g., converting from Date to String, but none universally recognized.

    Other languages may have a different set of native types that must be serialized carefully to deal with this type of conversion.

    (emphasize added)

    PS: ok Wikipedia is probably not the most reliable source for citations but my resources for free consultation is limited. YMMV ;)

Re: Outputting JSON with function () {...} values
by haukex (Archbishop) on Oct 22, 2018 at 19:05 UTC

    As the others have said, that's not JSON, and it's unclear what Perl structure you would want to translate to a JS function. Could you show an example of such a Perl data structure, and the equivalent JavaScript that you expect to get?

    Anyway, for WebPerl, I also looked into exactly this question and found that at least Cpanel::JSON::XS, and it seems the other JSON modules too, do not provide a good way to hook into the serialization to accomplish this. I ended up writing my own simple serializer. Something like this:

    use warnings; use strict; use Cpanel::JSON::XS; our $JSON = Cpanel::JSON::XS->new->allow_nonref; sub to_js { my $what = shift; if (my $r = ref $what) { if ($r eq 'HASH') { return '{' . join(',', map { $JSON->encode("$_").':'.to_js($$what{$_}) } sort keys %$what ) . '}'; } elsif ($r eq 'ARRAY') { return '[' . join(',', map { to_js($_) } @$what) . ']'; } elsif ($r eq 'CODE') { return 'function () { /* some JS here? */ }'; } else { die "can't encode ref $r to JS" } } else { return $JSON->encode($what) } } print to_js( { foo => [ "bar", "quz" ], baz => sub {}, } ), "\n"; __END__ {"baz":function () { /* some JS here? */ },"foo":["bar","quz"]}

      I've been looking at JSON "things" if only to be able to figure out a taxonomy for them. I get the same output as haukex does from the preceding script, but I would think they this should be decodable as JSON. Instead, I get an error:

      $ $ ./1.js.pl {"baz":function () { /* some JS here? */ },"foo":["bar","quz"]} $ ./1.json.pl 'false' expected, at character offset 7 (before "function () { /* som. +..") at ./1.json.pl line 7, <DATA> line 1. $ cat 1.json.pl #!/usr/bin/perl -w use 5.011; use Data::Dumper; use JSON; my $json = <DATA>; my $perl = decode_json $json; print Dumper $perl; __DATA__ {"baz":function () { /* some JS here? */ },"foo":["bar","quz"]} $
        this should be decodable as JSON

        Note that JSON is only a subset of JavaScript, you can see the grammar e.g. here - it doesn't even allow comments. That's why Perl's most common JSON modules, or browsers, won't handle it. Although the example code can be parsed with JavaScript's eval, of course that's a huge security hole because it allows for arbitrary code execution, which is one of the reasons why JSON was invented in the first place.

        > should be decodable as JSON

        The OP didn't ask for real JSON but a nested JS data structure, which is an upper set.

        This can't be decoded, Perl doesn't have a builtin JS parser (yet).

        The only way I see is to serialize objects like this function as string - hence lethal JSON - and to have a conversion filter on the JS side before using it (here with eval)

        Though I'm not sure about the best way to mark such objects to be distinguished from normal strings.

        See also point 3

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Outputting JSON with function () {...} values
by Your Mother (Archbishop) on Oct 22, 2018 at 16:57 UTC

    And even if it were valid JSON, it couldn't work (at least not without a transpiler or something that does not currently exist). It assumes something along these lines is possible to write, and for it to be meaningful if you could—

    print to_json({ a => sub(){\1} });

    Update, removed extra brace.

      IMHO it's theoretically possible to convert core JS (i.e. neither DOM nor BOM) to Perl.

      But I have problems to see for what purpose.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Outputting JSON with function () {...} values
by LanX (Saint) on Oct 22, 2018 at 20:02 UTC
    Seems like I misunderstood the direction of your process.

    you want to create "pseudo" JSON from Perl with a JSON library.

    There is a possibility to allow Perl objects to return a scalar, but I doubt it's possible to be js code instead of a string.

    See https://metacpan.org/pod/JSON#OBJECT-SERIALISATION

    I see 3 possibilities:

    • change your representation to legal JSON
    • regex valid JSON to strip the quotes from strings starting with "function..."
    • do the same on the receiver side in JS by walking your data and eval'ing those strings.

    HTH

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Yes that's correct. Regexp'ing afterwords was where I thought I'd end up and this has confirmed it. I can't change to "legal JSON" as the output if for a JS plugin and I don't want to start hacking at that and break future upgrades.

        It's not trivial if the code contains double quotes and you do it on the Perl side.

        Post processing in JS should be easier.

        > and this has confirmed it.

        Please don't consider my opinion as the only possible.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice