Skip to end of metadata
Go to start of metadata

The class LoxBerry::JSON is intended to simplify working with json configuration files. It abstracts reading, writing and change recognition, and presents the content of the json file as an object variable. It supports simple key/value pairs, and also json objects and arrays. 

The advanced features allow to search for values in arrays and hashes.

LoxBerry Compatibility

This object class is in available from V1.4.0. Set the minimum LoxBerry version in your plugin.cfg.

Inclusions

This library automatically includes:

  • use JSON;

Abstract

use LoxBerry::JSON;
my $cfgfile = $lbpplugindir."/pluginconfig.json";

$jsonobj = LoxBerry::JSON->new();
$cfg = $jsonobj->open(filename => $cfgfile);

# Read values
print "Firstname: " . $cfg->{Firstname};
print "Surname: " . $cfg->{Surname};

# Change values
$cfg->{Firstname} = "Christian";
$cfg->{Surname} = "Fenzl";

# Write 
$jsonobj->write();
# On every write, the class checks for changes and omits writing if nothing has changed

## Specials ##

# Parse an already existing json string (available from LB 1.4.1)
my $jsonstring = ' { "Firstname": "christian", "Surname": "fenzl" } ';
$cfg = $jsonobj->parse($jsonstring);

# Return a flat key=value hash from a hierarchical data structure (available from LB 2.0)
my $flat_hashref = $jsonobj->flatten();
my $flat_hashref = $jsonobj->flatten($prefix);

# Use $jsonobj in HTML::Template associate (available from LB 2.0)
my $template = HTML::Template->new(
    filename  => 'json_param.tmpl',
    associate => $jsonobj,
);

# Encode current data hash to json string, optional pretty printing (available from LB 2.0)
my $jsonstr = $jsonobj->encode();
my $jsonstr = $jsonobj->encode( pretty => 1 );

# Generate ready and escaped JavaScript code from current data (available from LB 2.0)
print $jsonobj->jsblock('jsvariablename');
# Returns: cfg = JSON.parse('<your data as json correctly escaped>');

# Enable debugging and dumping
$LoxBerry::JSON::DEBUG = 1; # Enables debug messages
$LoxBerry::JSON::DUMP = 1;  # Enables dumping of the current dataset

# Keep the file readonly in every case
$cfg = $jsonobj->open(filename => $cfgfile, readonly => 1);

# Write automatically on close
$cfg = $jsonobj->open(filename => $cfgfile, writeonclose => 1);

# Returns the filename
my $filename = $jsonobj->filename();
# Changes the filename (available from LB 1.4.1)
$jsonobj->filename('/tmp/newfilename.json');

# Find
my @result = $jsonobj->find($cfg->{namesarray}, "$_->{Name} eq 'Christian'");
# See the detailed description for use-cases

# Dump
$jsonobj->dump{$cfg->{namesarray});

Parameter

ParameterOptionalDefaultDescription
filename

JSON file, that should be opened. If the file doesn't exist, it will be created on write.

writeonclose

x0

If 0, you need to explicitely call write. write can also be called multiple times.

If 1, the file is written automatically on descruction of the object.

readonlyx0If 1, every writing to the file is omitted.

Basically, the function always compares the initial content with the new content, and only writes, if the content has changed. This also happens when write is called explicitely.

With writeonclose => 1 is not written immediately on change of a value, but if the descructor is called, that means

  • on leaving the scope of the $jsonparser object (thereforce, define $jsonparser in the main section, if you want to use it globally)
  • on exit of the program.

Open and change JSON - Example

#!/usr/bin/perl
use LoxBerry::System;
use LoxBerry::JSON;

$LoxBerry::JSON::DEBUG = 1;
$LoxBerry::JSON::DUMP = 1;

my $jsonparser = LoxBerry::JSON->new();
my $config = $jsonparser->open(filename => "/tmp/somefile.json", writeonclose => 0);

# Error handling
if (!$config) {
	print "Error loading file\n";
} else {
	print "File loaded\n";
}

print "Version of the file: $config->{Version}\n";

# Simple values
$config->{Info} = "Write data to JSON";
$config->{Version} = $config->{Version} + 1;

# Creating a json object MINISERVER_HASH with nested values	
$config->{MINISERVER_HASH}->{1}->{Name} = "MSOG";
$config->{MINISERVER_HASH}->{2}->{Name} = "MSUG";

# Creating an array 'Colors'
my @colors = ( "red", "blue", "green");
$config->{Colors} = \@colors;

# Creating a json object called 'Server' with some data
my %settings = ( "ip" => "192.168.0.1",
				 "port" => "8000",
				 "protocol" => "tcp"
				);
$config->{Server} = \%settings;
				
$jsonparser->write();

JSON Result

{
   "Colors" : [
      "red",
      "blue",
      "green"
   ],
   "Info" : "Write data to JSON",
   "MINISERVERS" : {
      "1" : {
         "Name" : "MSOG"
      },
      "2" : {
         "Name" : "MSUG"
      }
   },
   "Server" : {
      "ip" : "192.168.0.1",
      "port" : "8000",
      "protocol" : "tcp"
   },
   "Version" : 1
}

Search in JSON

The module implements a find function to search for elements in hashes and arrays.

my @result = $jsonparser->find(@/%element_to_search, $condition);

ParameterDescription
@/%element_to_search

The element_to_search must be an ARRAY (@something) or a HASH (%something).

Most likely you would send an object of the readed JSON, e.g. $config->{colors}

$condition

This must be a string that contains a valid Perl if condition, that is evaluated. \$_ is the current object in the condition.

Keep an eye on string interpolation:

  • Non-escaped sequences are interpolated BEFORE of the function evaluation
  • Escaped sequences are interpolated IN the funcation evaluation

Conditions must be able to be evaluated inside an if statement. Precisely it is evaluated as if ($perl_condition).

In the $condition, the $_ is the current evaluated object of the array/hash. Therefore, in your $condition you have to escape the element with \$_ .

In an ARRAY, \$_ therefore is the value of the array.

In an HASH, \$_ is the current evaluated object.

Both datatypes return an array with a list of the keys. In an ARRAY evaluation, the returning array holds the key number (0, 1, 2 etc). In an HASH evaluation, the returning array holds the key name of the found elements.

Double-check escaping in your condition!

my $house = "green";

my $condition_doublequotes = "\$_ eq \"$house\"";  → The function evaluates $_ eq "green" → OK

my $condition_doublequotes = "\$_ eq \$house"; → The function evaluates $_ eq $house. As $house is not defined in the function, it will raise an exception.

my $condition_singlequoutes = '$_ eq "' . $house . '"';  → The function evaluates $_ eq "green" → OK

my $condition_singlequoutes = '$_ eq "$house"';  → The function evaluates $_ eq "$house". As $house is not defined in the function, it will raise an exception.


See the examples.

#!/usr/bin/perl
use LoxBerry::System;
use LoxBerry::JSON;

## HASH search ##

# Creating a nested hash (1, 2 are names, not array elements)
$config->{MINISERVER_HASH}->{1}->{Name} = "MSOG";
$config->{MINISERVER_HASH}->{2}->{Name} = "MSUG";

# Search for a Miniserver named 'MSUG', returns an array with the hash keys
my @result = $jsonparser->find($config->{MINISERVER_HASH}, "\$_->{Name} eq 'MSUG'");
# Dump the result to STDERR
$jsonparser->dump(\@result, "Result of Hash search");

## ARRAY search ##

# Creating an array 'Colors'
my @colors = ( "red", "blue", "green");
$config->{Colors} = \@colors;


# Search for the colors 'red' or 'green', returns an array with the element indexes of the array
my @result = $jsonparser->find($config->{Colors}, "\$_ eq 'red' or \$_ eq 'green'");
# Dump the result to STDERR
$jsonparser->dump(\@result, "Result of Array search");

Parse a JSON

If you would like to parse through a JSON, you should use Perl's key function:

use LoxBerry::JSON;
my $jsonparser = LoxBerry::JSON->new();
my $config = $jsonparser->open(filename => "/tmp/somefile.json");
foreach my $key (keys %$config) {
                print "The key is: " . $key . "\n";
				print "The value is: " . $config->{$key} . "\n";
}

Parse a JSON, sort by an attribute

If you have an unsorted list of objects, you can sort the objects by an attribute of the object:

use LoxBerry::JSON;
my $jsonparser = LoxBerry::JSON->new();
my $config = $jsonparser->open(filename => "/tmp/somefile.json");
foreach my $key ( sort { $config->{$a}->{title} cmp $config->{$b}->{title} } keys %$config) {
				print "The title is: " . $config->{$key}->{title} . "\n";
}

flatten: Get a flat key=value hash from json (available from LoxBerry 2.0)

The flatten function extracts the hierarchie of the json, and returns a flattened hash of the data structure. The delimiter of each hierarchie is the dot (.).

All named elements in the json are named in the hash. All non-named elements (e.g. arrays) will get index numbers as hierarchy. You should use Data::Dumper to view the output.

Example:

use LoxBerry::JSON;
my $jsonparser = LoxBerry::JSON->new();
my $config = $jsonparser->open(filename => "/tmp/somefile.json");
my $hashref = $jsonparser->flatten();
print $hashref->{"SMTP.SMTPSERVER"};

# You may want a prefix for your flattened hash
my $hashref = $jsonparser->flatten("CONFIG");
print $hashref->{"CONFIG.SMTP.SMTPSERVER"};

Try the living example in our testing folder: /opt/loxberry/libs/perllib/LoxBerry/testing/json_flat.pl

Use the json content in HTML::Template (available from LoxBerry 2.0)

You can directly assign the json content to HTML::Template by the associate command from HTML::Template:

use HTML::Template;
use LoxBerry::JSON;
my $jsonparser = LoxBerry::JSON->new();
my $config = $jsonparser->open(filename => "/tmp/somefile.json");

my $template = HTML::Template->new(
    filename  => 'template.html',
    associate => $jsonparser,
);

print $template->output();

Try the living example in our testing folder: /opt/loxberry/libs/perllib/LoxBerry/testing/json_param.pl

encode: Get the current data hash as json (available from LoxBerry 2.0)

To get the current data as json string. The json is not formatted with linebreaks and intends. With the optional parameter pretty => 1 you get a pretty json with linebreaks and intends.

Keep in mind, that the data is plain json. To use the string in JavaScript, use the method ->jsblock , as it also escapes the json for JavaScript.

use LoxBerry::JSON;
my $jsonparser = LoxBerry::JSON->new();
$jsonparser->open(filename => "/tmp/somefile.json");

my $jsonstr = $jsonparser->encode();
my $jsonstr_pretty = $jsonparser->encode( pretty => 1 );

Try the living example in our testing folder: /opt/loxberry/libs/perllib/LoxBerry/testing/json_encode.pl

jsblock: Generate a ready string to insert into your <script> block (available from LoxBerry 2.0)

The method returns a finally and ready-escaped varname = JSON.parse(...); statement to print into your JavaScript block. Use the parameter varname => "jsvariablename" to set the JS variable name.

use LoxBerry::JSON;
my $jsonparser = LoxBerry::JSON->new();
$jsonparser->open(filename => "/tmp/somefile.json");

print "<script>";
print $jsonparser->jsblock( varname => "config" );
print "console.log(config.SMTP.EMAIL);"
print "</script>";

Result of this code:

<script>
config = JSON.parse('{"SMTP":{"EMAIL":"my@mail.com"}}');
console.log(config.SMTP.EMAIL);
</script>

Try the living example in our testing folder: /opt/loxberry/libs/perllib/LoxBerry/testing/json_jsblock.cgi