Home > Net >  JSONizing nested Perl objects
JSONizing nested Perl objects

Time:09-17

I wrote some Perl class (package) that has a method to return a JSONized object presentation (i.e.: a JSON string). When refactoring my class uses objects from another class, and I had to adjust the original JSON-returning method.

My old class uses a helper function like this:

use JSON;
sub _JSON_string($$)
{
    my $data = shift;
    my $JSON = JSON->new();     # JSON encoder

    $JSON->utf8(1);
    $JSON->allow_blessed(1);
    $JSON->convert_blessed(1);
    if ($#_ >= 0 && $_[0]) {
        $JSON->indent(1);
        $JSON->indent_length($_[0]);
    }
    return $JSON->encode($data);
}

The extra optional argument is for pretty-printing (as you might have guessed). And the actual "JSONizer" looks like this:

sub JSON_string($;$)
{
    my $self = shift;
    my $hash = ...;

    return _JSON_string($hash, $_[0]);
}

In the object being used inside I wrote this code to JSONize:

use JSON;
sub TO_JSON($)
{
    my $self = shift;
    my $JSON = JSON->new();     # JSON encoder

    $JSON->utf8(1);
    $JSON->allow_blessed(1);
    $JSON->convert_blessed(1);
    return $JSON->encode({ ... });
}

Basically that works, but I'd like to pass on the "pretty-print" to TO_JSON somehow. The easiest solution would be to pass the original JSON object from the containing class to TO_JSON but that does not work (extra parameter not allowed). So it seems I need to pass the JSON object from the outer object to the inner objects for use of a common output style.

Sub-question: Why do I need to set allow_blessed(1) and convert_blessed(1) inside TO_JSON? That object does not have further blessed components (The ... consists of components inside the blessed object only; they are just numbers, strings, or undef).

Sample Data

(Simplified, just to show the structure) My object's data are arrays, not hashes:

  DB<3> x $mp
0  MonitoringParser=ARRAY(0x8e57c0)
   0  0
   1  'OK'
   2  '/etc/group: alpha=0.125, ...'
   4  HASH(0x1411608)
      'avg' => PerfData=ARRAY(0x1364ca0)
         0  'avg'
         1  0.00145
         2  undef
         3  undef
         4  undef
         5  0
         6  undef
      'exp_avg' => PerfData=ARRAY(0x12c6c80)
         0  'exp_avg'
         1  0.00052
         2  undef
         3  undef
         4  undef
         5  0
         6  undef
      'last' => PerfData=ARRAY(0x1549e50)
         0  'last'
         1  0.00051
         2  undef
         3  undef
         4  undef
         5  0
         6  undef
  DB<6> x $mp->perf_data->{'avg'}
0  PerfData=ARRAY(0x1364ca0)
   0  'avg'
   1  0.00145
   2  undef
   3  undef
   4  undef
   5  0
   6  undef
  DB<7> x $mp->perf_data->{'avg'}->as_string
0  'label="avg", value=0.00145, unit=<undef>, warn=<undef>, crit=<undef>, min=0, max=<undef>'

So the main object is MonitoringParser, and it has a hash ref with performance data at slot 4. The hash values are PerfData objects (also arrays). My objects have a as_string method that makes a human-readable presentation from the object. As you can see the array slots have a "legend" (label) each.

I want the JSON presentation to have those labels, too, so I wanted to build a hash from the array on the fly that adds keys to the values (The labels are available as an array via a class constant).

CodePudding user response:

TO_JSON needs to return a data structure to serialize, and it will be serialized as using that serializer's formatting choices/options.

It's not normally a JSON string itself. When it is, you end up with double-encoded JSON as you've discovered. This hurts both readability and usability.

You expect the JSON serializer to simply embed your string, which makes absolutely no sense. You can't insert arbitrary strings into a JSON document. While your string is also JSON, it might not be compatible with document into which it is being embedded. Differences in settings could easily result in a invalid document and break things.

CodePudding user response:

As mentioned in the answer by @ikegami you should return a data structure from TO_JSON(). Here is an example where I just convert a blessed reference to an array to a simple array containing the same data:

use feature qw(say);
use strict;
use warnings;
use JSON;

my $JSON = JSON->new();
$JSON->utf8(1);
$JSON->allow_blessed(1);
$JSON->convert_blessed(1);
my $data = {
    foo => "bar",
    a => [1, 2, {c => 4}],
    hash => { avg => PerfData->new( 'avg', 0.00145 ) }
};
my $json = $JSON->encode($data);
say $json;


package PerfData;
use feature qw(say);
use strict;
use warnings;

sub new {
    my ( $class, @args ) = @_;

    return bless [@args], $class;
}

sub TO_JSON {
    my $self = shift;
    return [@$self];
}

Output:

{"a":[1,2,{"c":4}],"hash":{"avg":["avg",0.00145]},"foo":"bar"}
  • Related