I'm working with moose objects in perl. I want to be able to covert the moose objects I make directly to JSON.
However, when I use use MooseX::Storage to covert the objects, it includes a hidden attribute that I don't know how to remove the "__CLASS__" .
Is there a way to remove this using MooseX::Storage ? (For now I am just using MooseX::Storage to covert it and using JSON to remove the "__ CLASS __ " attribute by going to a hash . ) The solution I am doing for now is a problem, because I have to do it everytime I get the JSON for every object(so when I write the JSON output to a file, to be loaded I have to make the changes everytime, and any referanced objects also have to be handled)
package Example::Component;
use Moose;
use MooseX::Storage;
with Storage('format' => 'JSON');
has 'description' => (is => 'rw', isa => 'Str');
1;
no Moose;
no MooseX::Storage;
use JSON;
my $componentObject = Example::Component->new;
$componentObject->description('Testing item with type');
my $jsonString = $componentObject->freeze();
print $jsonString."\n\n";
my $json_obj = new JSON;
my $perl_hash = $json_obj->decode ($jsonString);
delete ${$perl_hash}{'__CLASS__'};
$jsonString = $json_obj->encode($perl_hash);
print $jsonString."\n\n";
MooseX::Storage is not particularly suited to this task. It's designed to enable persistent storage of Moose objects (that's why it adds the __CLASS__ field) so they can be retrieved by your program later.
If your goal is to construct objects for a JSON API, then it would probably be much easier to just pass your object's hashref directly to JSON.pm.
use JSON -convert_blessed_universally;
my $json_obj = JSON->new->allow_blessed->convert_blessed;
my $jsonString = $json_obj->encode( $componentObject );
The -convert_blessed_universally option (in addition to being a mouthful) will cause JSON.pm to treat blessed references (objects) as ordinary Perl structures which can be translated to JSON directly.
EDIT: Looks like you have to add the allow_blessed and convert_blessed options to the JSON object also.
Related
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,
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.
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);
All of the documentation and examples I have seen for the Perl JSON::XS module use a OO interface, e.g.
print JSON::XS->new->ascii()->pretty()->canonical()->encode($in);
But I don't necessarily want all those options every time, I'd prefer to send them in a hash like you can with the basic JSON module, e.g.
print to_json($in, { canonical => 1, pretty => 1, ascii => 1 } );
sending to that encode_json yields
Too many arguments for JSON::XS::encode_json
Is there any way to do that?
JSON's to_json uses JSON::XS if it's installed, so if you want a version of to_json that uses JSON::XS, simply use the one from JSON.
Or, you could recreate to_json.
sub to_json
my $encoder = JSON::XS->new();
if (#_ > 1) {
my $opts = $_[1];
for my $method (keys(%$opts)) {
$encoder->$_($opts->{$_});
}
}
return $encoder->encode($_[0]);
}
But doesn't help stop passing in the options every time. If you're encoding multiple data structures, it's best to create a single object and reuse it.
my $encoder = JSON::XS->new->ascii->pretty->canonical;
print $encoder->encode($in);
Run cpanm --look DBIx::Class ; cd examples/Schema/ to use the example database.
use 5.024;
use strictures;
use JSON::MaybeXS qw(encode_json);
use MyApp::Schema qw();
use Sub::Install qw();
my $s = MyApp::Schema->connect('dbi:SQLite:db/example.db');
# Yes, I know Helper::Row::ToJSON exists.
Sub::Install::install_sub({
code => sub {
my ($self) = #_;
return { map {$_ => $self->$_} keys %{ $self->columns_info } };
},
into => $s->source('Track')->result_class,
as => 'TO_JSON',
});
my ($t) = $s->resultset('Cd')->first->tracks;
say ref $t->can('TO_JSON'); # 'CODE', ok
say ref $t->TO_JSON; # 'HASH', ok
say encode_json $t;
# encountered object 'MyApp::Schema::Result::Track=HASH(0x1a53b48)',
# but neither allow_blessed, convert_blessed nor allow_tags settings
# are enabled (or TO_JSON/FREEZE method missing) at …
I expect the serialiser to find the installed hook and use it, but instead I get the error above. What's going wrong?
In order to make JSON::XS consider TO_JSON, you have to explicitly enable convert_blessed option:
my $coder = JSON::XS->new;
$coder->convert_blessed(1);
say $coder->encode($t);
According to docs:
$json = $json->convert_blessed ([$enable])
$enabled = $json->get_convert_blessed
See "OBJECT SERIALISATION" for details.
If $enable is true (or missing), then encode, upon encountering a blessed object, will check for the availability of the TO_JSON method
on the object's class. If found, it will be called in scalar context
and the resulting scalar will be encoded instead of the object.
The TO_JSON method may safely call die if it wants. If TO_JSON returns other blessed objects, those will be handled in the same way.
TO_JSON must take care of not causing an endless recursion cycle (==
crash) in this case. The name of TO_JSON was chosen because other
methods called by the Perl core (== not by the user of the object) are
usually in upper case letters and to avoid collisions with any to_json
function or method.
If $enable is false (the default), then encode will not consider this type of conversion.
This setting has no effect on decode.
(emphasis mine)
I load models with relations (like a book, authors, publisher, keywords) and send it out to the web interface in JSON. Users will edit it there, and then the interface will send it back as JSON. The question is how do I create a model from the JSON (the opposite of toJson() call) and then save it to the database. It would also be helpful if I could compare the original data - reloaded from the db again - with the data I receive from the web layer.
You can decode the JSON once it's received by the server:
$decoded = json_decode( Input::get('json') );
If you want to compare the models one option is grabbing the ID of the model from your decoded JSON (make sure you double check the user has access to it in case they try fudging the data on you), loop over your key/values for your decoded data and match them against each other.
$model = YourModel::find( $decoded->id ); // dont forget to ensure they have access to this model
// Set up an empty array to store anything that's changed
$changes = array();
// Loop over your decoded values
foreach( $decoded as $key => $value ) {
// If the value in your model doesn't match the decoded value, add to the changes array
if( $model->{$key} != $value ) {
$changes[$key] = $value;
}
}
you can convert it to a collection by using collect(json_decode($json)).
collection Docs
I'm using a service to load my form data into an array in my angular2 app.
The data is stored like this:
arr = []
arr.push({title:name})
When I do a console.log(arr), it is shown as Object. What I need is to see it
as [ { 'title':name } ]. How can I achieve that?
you may use below,
JSON.stringify({ data: arr}, null, 4);
this will nicely format your data with indentation.
To print out readable information. You can use console.table() which is much easier to read than JSON:
console.table(data);
This function takes one mandatory argument data, which must be an array or an object, and one additional optional parameter columns.
It logs data as a table. Each element in the array (or enumerable property if data is an object) will be a row in the table
Example:
first convert your JSON string to Object using .parse() method and then you can print it in console using console.table('parsed sring goes here').
e.g.
const data = JSON.parse(jsonString);
console.table(data);
Please try using the JSON Pipe operator in the HTML file. As the JSON info was needed only for debugging purposes, this method was suitable for me. Sample given below:
<p>{{arr | json}}</p>
You could log each element of the array separately
arr.forEach(function(e){console.log(e)});
Since your array has just one element, this is the same as logging {'title':name}
you can print any object
console.log(this.anyObject);
when you write
console.log('any object' + this.anyObject);
this will print
any object [object Object]