229 lines
4.9 KiB
Perl
229 lines
4.9 KiB
Perl
# This code was forked from the LiveJournal project owned and operated
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
|
# currently be found at:
|
|
#
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
|
#
|
|
# In accordance with the original license, this code and all its
|
|
# modifications are provided under the GNU General Public License.
|
|
# A copy of that license can be found in the LICENSE file included as
|
|
# part of this distribution.
|
|
|
|
package LJ::JSON;
|
|
use strict;
|
|
|
|
use base qw(Exporter);
|
|
@LJ::JSON::EXPORT = qw( from_json to_json );
|
|
|
|
=head1 NAME
|
|
|
|
LJ::JSON - Wrapper for JSON which handles text encoding
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use LJ::JSON; # never use JSON! That could introduce subtle encoding issues
|
|
|
|
my $is_yummy = 1;
|
|
my $json_string = to_json( { a => "apple",
|
|
yummy => LJ::JSON->to_boolean( $is_yummy ) # true/false
|
|
yummy2 => $is_yummy # 1/0
|
|
# both can work in JS, but prefer true/false
|
|
} );
|
|
my $object = from_json( q!{ "a": "apple" }! );
|
|
|
|
=cut
|
|
|
|
my $wrap;
|
|
|
|
sub to_json ($@) {
|
|
my (@args) = @_;
|
|
return $wrap->encode(@args);
|
|
}
|
|
|
|
sub from_json ($@) {
|
|
my ($dump) = @_;
|
|
|
|
return unless $dump;
|
|
return $wrap->decode($dump);
|
|
}
|
|
|
|
sub class {
|
|
return ref $wrap;
|
|
}
|
|
|
|
sub true { $wrap->true }
|
|
sub false { $wrap->false }
|
|
|
|
sub to_boolean {
|
|
my ( $class, $what ) = @_;
|
|
return $what ? $wrap->true : $wrap->false;
|
|
}
|
|
|
|
sub to_number {
|
|
my ( $class, $what ) = @_;
|
|
|
|
# not using int deliberately because we may be handling floats here
|
|
return $what + 0;
|
|
}
|
|
|
|
foreach my $class (qw(LJ::JSON::XS LJ::JSON::JSONv2 LJ::JSON::JSONv1)) {
|
|
if ( $class->can_load ) {
|
|
$wrap = $class->new;
|
|
last;
|
|
}
|
|
}
|
|
die unless $wrap;
|
|
|
|
1;
|
|
|
|
package LJ::JSON::Wrapper;
|
|
|
|
use Encode qw();
|
|
|
|
sub traverse {
|
|
my ( $class, $what, $sub ) = @_;
|
|
|
|
my $type = ref $what;
|
|
|
|
# simple scalar
|
|
if ( $type eq '' ) {
|
|
return $sub->($what);
|
|
}
|
|
|
|
# hashref
|
|
if ( $type eq 'HASH' ) {
|
|
my %ret;
|
|
foreach my $k ( keys %$what ) {
|
|
$ret{ $sub->($k) } = $class->traverse( $what->{$k}, $sub );
|
|
}
|
|
return \%ret;
|
|
}
|
|
|
|
# arrayref
|
|
if ( $type eq 'ARRAY' ) {
|
|
my @ret;
|
|
foreach my $v (@$what) {
|
|
push @ret, $class->traverse( $v, $sub );
|
|
}
|
|
return \@ret;
|
|
}
|
|
|
|
# unknown type; let the subclass decode it to a scalar
|
|
# (base class function defaults to plain stringification)
|
|
return $sub->( $class->decode_unknown_type($what) );
|
|
}
|
|
|
|
sub traverse_fix_encoding {
|
|
my ( $class, $what ) = @_;
|
|
|
|
return $class->traverse(
|
|
$what,
|
|
sub {
|
|
my ($scalar) = @_;
|
|
|
|
return $scalar unless Encode::is_utf8($scalar);
|
|
|
|
# if the string does indeed contain wide characters (which happens
|
|
# in case the source string literals contained chars specified as
|
|
# '\u041c'), encode stuff as utf8
|
|
if ( $scalar =~ /[^\x01-\xff]/ ) {
|
|
return Encode::encode( "utf8", $scalar );
|
|
}
|
|
|
|
return Encode::encode( "iso-8859-1", $scalar );
|
|
}
|
|
);
|
|
}
|
|
|
|
sub decode_unknown_type {
|
|
my ( $class, $what ) = @_;
|
|
|
|
return "$what";
|
|
}
|
|
|
|
package LJ::JSON::XS;
|
|
|
|
our @ISA;
|
|
BEGIN { @ISA = qw(LJ::JSON::Wrapper JSON::XS); }
|
|
|
|
sub can_load {
|
|
eval { require JSON::XS; JSON::XS->import; };
|
|
return !$@;
|
|
}
|
|
|
|
sub new {
|
|
my ($class) = @_;
|
|
return $class->SUPER::new->latin1;
|
|
}
|
|
|
|
sub decode {
|
|
my ( $class, $dump ) = @_;
|
|
|
|
my $decoded = $class->SUPER::decode($dump);
|
|
$decoded = $class->traverse_fix_encoding($decoded);
|
|
return $decoded;
|
|
}
|
|
|
|
sub decode_unknown_type {
|
|
my ( $class, $what ) = @_;
|
|
|
|
# booleans get converted to undef for false and 1 for true
|
|
return $what ? 1 : 0 if JSON::XS::is_bool($what);
|
|
|
|
# otherwise, stringify
|
|
return "$what";
|
|
}
|
|
|
|
1;
|
|
|
|
package LJ::JSON::JSONv2;
|
|
|
|
our @ISA;
|
|
BEGIN { @ISA = qw(LJ::JSON::Wrapper JSON); }
|
|
|
|
sub can_load {
|
|
eval { require JSON };
|
|
return !$@ && $JSON::VERSION ge 2;
|
|
}
|
|
|
|
sub new {
|
|
my ($class) = @_;
|
|
return $class->SUPER::new->latin1;
|
|
}
|
|
|
|
sub decode {
|
|
my ( $class, $dump ) = @_;
|
|
|
|
my $decoded = $class->SUPER::decode($dump);
|
|
$decoded = $class->traverse_fix_encoding($decoded);
|
|
return $decoded;
|
|
}
|
|
|
|
sub decode_unknown_type {
|
|
my ( $class, $what ) = @_;
|
|
|
|
# booleans get converted to 0 for false and 1 for true
|
|
return $what ? 1 : 0 if JSON::is_bool($what);
|
|
|
|
# otherwise, stringify
|
|
return "$what";
|
|
}
|
|
|
|
1;
|
|
|
|
package LJ::JSON::JSONv1;
|
|
|
|
our @ISA;
|
|
BEGIN { @ISA = qw(LJ::JSON::Wrapper JSON); }
|
|
|
|
sub can_load {
|
|
eval { require JSON };
|
|
return !$@ && $JSON::VERSION ge 1;
|
|
}
|
|
|
|
*encode = \&JSON::objToJson;
|
|
*decode = \&JSON::jsonToObj;
|
|
|
|
1;
|