[Catalyst] formfu->JSON

Charles Bailey bailey.charles at gmail.com
Tue Nov 11 02:19:15 GMT 2008


On Mon, Nov 10, 2008 at 3:24 PM, Paul Tichonczuk <paultk at gmail.com> wrote:
> Hi,
> I'm new to Catalyst.
> I've gone through both the Catalyst Tutorial and the Catalyst book.
>
> I'm trying out FormFu + Jemplate.
>
> Basic problem I've come up to is redering a FormFu form into JSON for
> sending to the client.
>
> I keep getting:
> [error] Caught exception in MyApp::View::JSON->process "encountered
> object 'MyApp::Controller::Books=3DHASH(0x9f4406c)', but neither
> allow_blessed nor convert_blessed settings are enabled at
> /usr/local/share/perl/5.8.8/JSON/Any.pm line 426."

It looks as though you're trying to render your controller object into
the JSON response.  By default, C::V::JSON tries to serialize all keys
in the stash; have you perhaps stored some object references there?

If your intent is to render only specific values from the hash, you
should look at the expose_stash configuration option to C::V::JSON.

If you do want to serialize object, then you'll need to override the
default encode_json() and use a JSON encoder with the convert_blessed
or allow_blessed options set, perhaps combined with TO_JSON() in the
object class(es).  I've attached a (very underdeveloped and
underdocumented) utility class that I've found helpful  when dealing
with simple hash-based classes.

-- =

Regards,
Charles Bailey
Lists: bailey _dot_ charles _at_ gmail _dot_ com
Other: bailey _at_ newman _dot_ upenn _dot_ edu
-------------- next part --------------
#!/usr/local/bin/perl
#

use 5.010;
package JSON::Able;
use Moose::Role;


use Carp qw(croak);
use Scalar::Util qw(blessed reftype refaddr);
use JSON qw();

our($VERSION) =3D '0.01';
our($REVISION) =3D '$Revision$' =3D~ /: (\d+)/ ? $1 : 0;

# Used only for non-Moose consumer classes
our(@EXPORT_OK) =3D qw(TO_JSON encode_json as_json
		     json_key_filter json_value_transform json_pretty);
our(%EXPORT_TAGS) =3D (all =3D> [ @EXPORT_OK ]);
sub import { require Exporter; goto &Exporter::import; }

# Track configuration options for target packages, since JSON::XS does
# not provide for any way to pass options to TO_JSON.  We could make
# this a wrapper around our own method which does accept options, but
# that would compete with Moose::Role's injection of methods . . .
my(%Config);

sub _get_config_data {
  my($thing,$create) =3D @_;
  my $pkg =3D blessed($thing) // $thing;
  if (ref $thing) {
    my $oid =3D (blessed($thing) || reftype($thing)) . '(' . refaddr($thing=
) . ')';
    return $Config{$oid} if exists $Config{$oid};
    return $Config{$pkg} if exists $Config{$pkg};
    $Config{$oid} ||=3D {} if $create;
    return $Config{$oid} || {};
  }
  $Config{$pkg} ||=3D {} if $create;
  return $Config{$pkg} || {};
}

sub _configure_option {
  my($opt,$thing,$val) =3D @_;
  my $cfg =3D _get_config_data($thing, @_ > 2);
  my $old =3D $cfg->{$opt};
  if (@_ > 2) {
    $cfg->{$opt} =3D $val;
  }
  $old;
}
  =

sub json_key_filter { _configure_option('KEY_FILTER', @_); }

sub json_value_transform { _configure_option('VALUE_TRANSFORM', @_); }

sub json_pretty { _configure_option('PRETTY', @_); }

sub TO_JSON {
  my $self =3D shift;
  my $pkg =3D blessed $self;
  my $vers =3D $self->VERSION;
  my $cfg =3D _get_config_data($self);
  my $key_filter =3D $cfg->{KEY_FILTER} || =

    sub { $_[0] =3D~ /^(?!_)/ };
  my $val_xform =3D $cfg->{TRANSFORM_VALUES} ||
    sub { my($val,$isset) =3D @_; return $isset ? $val : (); };
  my(%attrs,%tojson);

  # If you implement this interface, we assume you're descended from
  # or behave like Moose
  if ($self->can('meta') and
      $self->meta->can('compute_all_applicable_attributes')) {
    %attrs =3D map {$_->name =3D> $_->has_value($self) }
      $self->meta->compute_all_applicable_attributes;
  }
  elsif (reftype($self) eq 'HASH') {
    %attrs =3D map { $_ =3D> 1 } keys %$self;
  }
  else {
    croak("Don't know how to get attributes of \"$self\" for JSON");
  }

  foreach my $k (keys %attrs) {
    next unless $key_filter->($k);
    my $val =3D $self->can($k) ? $self->$k : $self->{$k};
    my(@rslt) =3D $val_xform->($val, $attrs{$k});
    next unless @rslt;
    $val =3D $rslt[0];
    # Special-case DateTime, which is used frequently in the project
    # hierarchy.  It doesn't provide a TO_JSON method, but has a
    # perfectly serviceable overloading of ""
    if (blessed($val) and $val->isa('DateTime')) { $val =3D "$val"; }
    $tojson{$k} =3D $val;
  }

  # Include some metadata to simplify recreating objects
  # if data ever comes back
  $tojson{_source_class} =3D $pkg;
  $tojson{_source_class_version} =3D $vers;

  \%tojson;
}

sub encode_json {
  my $cfg =3D _get_config_data($_[0]);
  JSON->new->pretty($cfg->{PRETTY} // 1)->
    convert_blessed->encode(shift);
}

*as_json =3D \&encode_json;

1;

__END__

=3Dhead1 NAME

JSON::Able - Role providing JSON serialization

=3Dhead1 SYNOPSIS

  # Moose-based class
  package My:Project::SomeData;
  with 'JSON::Able';

  package main;
  my $obj =3D My::Project::SomeData->new(\%data);

  require JSON;
  my $json =3D JSON->new->pretty->convert_blessed->encode($obj);
  my $same_json =3D $obj->as_json;
  my $still_same_json =3D $obj->encode_json;


  # Non-Moose-based class
  package Some::Class;
  use JSON::Able ':all';
  =

  package main;
  my $obj =3D Some::Class->new(foo =3D> 'bar', baz =3D> 'quux');

  require JSON;
  my $json =3D JSON->new->pretty->convert_blessed->encode($obj);
  my $same_json =3D $obj->as_json;
  my $still_same_json =3D $obj->encode_json;
  =

=3Dhead1 DESCRIPTION

This package implements a Moose role that provides for JSON
serialization of Moose attributes of the consuming class in a manner
compatible with L<JSON::XS>.

While designed for use with Moose-based classes, it can also be mixed
into any class implementing an object structure based on a hash
reference.  In this instance, you need to import at least the
L</TO_JSON> method for it to do anything useful.  Note that, as of this
writing, you cannot "JSON enable" a class simply by inheriting from
F<JSON::Able>, since L<JSON::XS> requires a F<TO_JSON>
method I<in the class to be serialized>, not just in one of its base classe=
s.

=3Dhead2 EXPORT

None by default, and none required when used as a Moose role.

If you are using this class directly to support JSON serialization of
a non-Moose-based class, you need to import at least L</TO_JSON>.  You
may also import F<as_json> and F<encode_json> if you desire.  Note
that L<JSON> exports the F<encode_json> method by default, =


=3Dhead1 BUGS AND CAVEATS

Are there, for certain, but have yet to be cataloged.

=3Dhead1 REVISION

$Revision$ $Date: 2008-03-26T18:38:58.710706Z $

=3Dhead1 AUTHOR

Charles Bailey, E<lt>bailey at newman.upenn.eduE<gt>

=3Dhead1 COPYRIGHT

Copyright (C) 2008 by Charles Bailey

=3Dcut


More information about the Catalyst mailing list