skip to Main Content

For some Perl diagnostic tests, I’m recording assorted bits of information formatted as JSON using JSON::MaybeXS.

I get an error when I want to record the current Perl version, which I obtain from the special variable $^V.

As the minimal demonstration script shows, the error occurs unless I quote $^V as "$^V".

json_perl_version_test.pl

#!/usr/bin/env perl

use strict;
use warnings;
use v5.18;

use JSON::MaybeXS;

say "Running Perl version $^V";

my $item    = 'Wut?';

my %hash1   = (
    something   => $item,
    v_unquoted => $^V
);
eval { say say 'Hash1: ', encode_json %hash1 };
say "Oops - JSON encode error: $@" if $@;

my %hash2   = (
    something   => $item,
    v_quoted => "$^V"
);
say 'Hash2: ', encode_json %hash2;

# Running Perl version v5.34.0
# Oops - JSON encode error: encountered object 'v5.34.0', 
# but neither allow_blessed, convert_blessed nor allow_tags
# settings are enabled (or TO_JSON/FREEZE method missing) at
# /Users/bw/Documents/Dev/tests/json_perl_version_test.pl line 17.

# Hash2: {"something":"Wut?","v_quoted":"v5.34.0"}

Note that it wasn’t necessary to quote $item.

The error message refers to some ways to handle other cases, but seemingly not including canonical Perl version dotted-decimal strings. I’ve looked through the main Perl JSON modules (recent versions of JSON::MaybeXS, JSON, and Cpanel::JSON::XS), but can’t find anything referring to $^V or dotted-decimal strings. Also don’t find a relevant question on SO :(.

Perhaps I’m missing something? Or am I stuck with needing to quote $^V?

Reasons?

Thanks,

2

Answers


  1. Blessed Perl objects can’t be stored in JSON without extra steps (mentioned by the error).

    print ref $^V;  # version
    

    A possible workaround:

    my $j = Cpanel::JSON::XS->new->convert_blessed;  # Allow stringification.
    say 'Hash1: ', $j->encode(%hash1);
    
    Login or Signup to reply.
  2. The $^V variable is really an object

    The revision, version, and subversion of the Perl interpreter, represented as a version object.

    An object cannot be stored in JSON just so. Quoting it stringifies it.


    It is possible to make JSON::XS (and its Cpanel::) take a blessed reference but it involves more work. See Object Serialization. The cleanest complete solution is with convert_blessed, when the encode method will look for a TO_JSON method (in the class of the object that is to be added to JSON), which would return a JSON-ready string.

    Alas, there is no such a thing for the version (nor for a few other classes I tried, like DateTime). One can add that method to the class but just thinking of it makes quotes look nice.

    Another way is to get explicit in making the version object stringify

    my $json = JSON::XS->new->encode( { ver => $^V->stringify } )
    

    This is yet more elaborate but at least now it’s clear what the matter is, without magic quotes.

    Or just quote it and add a comment.


    By "monkey-patching" it, for example

    • add the sub, with a fully qualified name

      perl -Mstrict -wE'use JSON::XS; say $^V; 
          sub version::TO_JSON { return $_[0]->stringify }; 
          my $json = JSON::XS->new->convert_blessed->encode( { ver => $^V } ); 
          say $json'
      

      Can also add a sub via string eval at runtime but that doesn’t seem needed.

    • write the code reference to the class’s symbol table at runtime

      perl -wE'use JSON::XS; say $^V; 
          *{"version"."::"."TO_JSON"} = sub { return $_[0]->stringify }; 
          $json = JSON::XS->new->convert_blessed->encode( { ver => $^V } ); 
          say $json'
      

      well, or really with strict in effect we need to allow the symbolic refs

      perl -Mstrict -wE'use JSON::XS; say $^V; 
          NO_STRICT_REFS: { 
              no strict "refs"; 
              my ($class, $method) = qw(version TO_JSON);
              *{$class."::".$method} = sub { return $_[0]->stringify } 
          }; 
          my $json = JSON::XS->new->convert_blessed->encode( { ver => $^V } ); 
          say $json'
      

      where I also added variables for class name and method, not necessary but better.

      This is packaged for far nicer use in Sub::Install

      use Sub::Install;
      Sub::Install::install_sub({
          code => sub { ... }, into => $package, as => $subname
      });
      

      There are expected defaults and a bit more in the module.

    Or, of course by writing a wrapper class or some such but that’s something yet else.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search