Skip to content

Commit

Permalink
added decode_json and encode_json methods to Mojo::JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Feb 18, 2014
1 parent 7673fb0 commit 85fe227
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 70 deletions.
3 changes: 2 additions & 1 deletion Changes
@@ -1,5 +1,6 @@

4.82 2014-02-18
4.82 2014-02-19
- Added decode_json and encode_json functions to Mojo::JSON.
- Fixed bug in "user_agent_online.t".

4.81 2014-02-15
Expand Down
85 changes: 49 additions & 36 deletions lib/Mojo/JSON.pm
Expand Up @@ -8,7 +8,7 @@ use Scalar::Util 'blessed';

has 'error';

our @EXPORT_OK = ('j');
our @EXPORT_OK = qw(decode_json encode_json j);

# Literal names
my $FALSE = bless \(my $false = 0), 'Mojo::JSON::_Bool';
Expand Down Expand Up @@ -43,67 +43,66 @@ my $WHITESPACE_RE = qr/[\x20\x09\x0a\x0d]*/;
sub decode {
my ($self, $bytes) = @_;

# Clean start
$self->error(undef);
my $res = eval { decode_json($bytes) };
if (!$res && (my $e = $@)) {
chomp $e;
$self->error($e);
}

return $res;
}

sub decode_json {
my $bytes = shift;

# Missing input
$self->error('Missing or empty input') and return undef unless $bytes;
die "Missing or empty input\n" unless $bytes;

# Remove BOM
$bytes =~ s/^(?:\357\273\277|\377\376\0\0|\0\0\376\377|\376\377|\377\376)//g;

# Wide characters
$self->error('Wide character in input') and return undef
unless utf8::downgrade($bytes, 1);
die "Wide character in input\n" unless utf8::downgrade($bytes, 1);

# Detect and decode Unicode
my $encoding = 'UTF-8';
$bytes =~ $UTF_PATTERNS->{$_} and $encoding = $_ for keys %$UTF_PATTERNS;
$bytes = Mojo::Util::decode $encoding, $bytes;

# Object or array
my $res = eval {
local $_ = $bytes;

# Leading whitespace
m/\G$WHITESPACE_RE/gc;

# Array
my $ref;
if (m/\G\[/gc) { $ref = _decode_array() }

# Object
elsif (m/\G\{/gc) { $ref = _decode_object() }
# Leading whitespace
local $_ = $bytes;
m/\G$WHITESPACE_RE/gc;

# Invalid character
else { _exception('Expected array or object') }
# Array
my $ref;
if (m/\G\[/gc) { $ref = _decode_array() }

# Leftover data
unless (m/\G$WHITESPACE_RE\z/gc) {
my $got = ref $ref eq 'ARRAY' ? 'array' : 'object';
_exception("Unexpected data after $got");
}
# Object
elsif (m/\G\{/gc) { $ref = _decode_object() }

$ref;
};
# Invalid character
else { _exception('Expected array or object') }

# Exception
if (!$res && (my $e = $@)) {
chomp $e;
$self->error($e);
# Leftover data
unless (m/\G$WHITESPACE_RE\z/gc) {
my $got = ref $ref eq 'ARRAY' ? 'array' : 'object';
_exception("Unexpected data after $got");
}

return $res;
return $ref;
}

sub encode { Mojo::Util::encode 'UTF-8', _encode_value($_[1]) }
sub encode { encode_json($_[1]) }

sub encode_json { Mojo::Util::encode 'UTF-8', _encode_value(shift) }

sub false {$FALSE}

sub j {
my $d = shift;
return __PACKAGE__->new->encode($d) if ref $d eq 'ARRAY' || ref $d eq 'HASH';
return __PACKAGE__->new->decode($d);
my $data = shift;
return encode_json($data) if ref $data eq 'ARRAY' || ref $data eq 'HASH';
return eval { decode_json($data) };
}

sub true {$TRUE}
Expand Down Expand Up @@ -381,6 +380,20 @@ C<u2028> and C<u2029> will always be escaped to make JSONP easier.
L<Mojo::JSON> implements the following functions, which can be imported
individually.
=head2 decode_json
my $array = decode_json($bytes);
my $hash = decode_json($bytes);
Decode JSON to Perl data structure and die if decoding fails.
=head2 encode_json
my $bytes = encode_json([1, 2, 3]);
my $bytes = encode_json({foo => 'bar'});
Encode Perl data structure to JSON.
=head2 j
my $bytes = j([1, 2, 3]);
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojo/Message.pm
Expand Up @@ -5,7 +5,7 @@ use Carp 'croak';
use Mojo::Asset::Memory;
use Mojo::Content::Single;
use Mojo::DOM;
use Mojo::JSON;
use Mojo::JSON 'j';
use Mojo::JSON::Pointer;
use Mojo::Parameters;
use Mojo::Upload;
Expand Down Expand Up @@ -143,7 +143,7 @@ sub is_limit_exceeded { !!shift->{limit} }
sub json {
my ($self, $pointer) = @_;
return undef if $self->content->is_multipart;
my $data = $self->{json} ||= Mojo::JSON->new->decode($self->body);
my $data = $self->{json} ||= j($self->body);
return $pointer ? Mojo::JSON::Pointer->new->get($data, $pointer) : $data;
}

Expand Down
7 changes: 3 additions & 4 deletions lib/Mojo/Transaction/WebSocket.pm
Expand Up @@ -3,7 +3,7 @@ use Mojo::Base 'Mojo::Transaction';

use Compress::Raw::Zlib 'Z_SYNC_FLUSH';
use Config;
use Mojo::JSON;
use Mojo::JSON qw(encode_json j);
use Mojo::Transaction::HTTP;
use Mojo::Util qw(b64_encode decode encode sha1_bytes xor_encode);

Expand Down Expand Up @@ -83,7 +83,7 @@ sub build_message {
$frame = {text => encode('UTF-8', $frame)} if ref $frame ne 'HASH';

# JSON
$frame->{text} = Mojo::JSON->new->encode($frame->{json}) if $frame->{json};
$frame->{text} = encode_json($frame->{json}) if $frame->{json};

# Raw text or binary
if (exists $frame->{text}) { $frame = [1, 0, 0, 0, TEXT, $frame->{text}] }
Expand Down Expand Up @@ -326,8 +326,7 @@ sub _message {
$msg = $out;
}

$self->emit(json => Mojo::JSON->new->decode($msg))
if $self->has_subscribers('json');
$self->emit(json => j($msg)) if $self->has_subscribers('json');
$op = delete $self->{op};
$self->emit($op == TEXT ? 'text' : 'binary' => $msg);
$self->emit(message => $op == TEXT ? decode('UTF-8', $msg) : $msg)
Expand Down
5 changes: 2 additions & 3 deletions lib/Mojo/UserAgent/Transactor.pm
Expand Up @@ -6,7 +6,7 @@ use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;
use Mojo::Content::Single;
use Mojo::JSON;
use Mojo::JSON 'encode_json';
use Mojo::Parameters;
use Mojo::Transaction::HTTP;
use Mojo::Transaction::WebSocket;
Expand Down Expand Up @@ -185,8 +185,7 @@ sub _form {

sub _json {
my ($self, $tx, $data) = @_;
$tx->req->body(Mojo::JSON->new->encode($data));
my $headers = $tx->req->headers;
my $headers = $tx->req->body(encode_json($data))->headers;
$headers->content_type('application/json') unless $headers->content_type;
return $tx;
}
Expand Down
7 changes: 3 additions & 4 deletions lib/Mojolicious/Command/get.pm
Expand Up @@ -4,7 +4,7 @@ use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
use Mojo::DOM;
use Mojo::IOLoop;
use Mojo::JSON;
use Mojo::JSON qw(encode_json j);
use Mojo::JSON::Pointer;
use Mojo::UserAgent;
use Mojo::Util qw(decode encode);
Expand Down Expand Up @@ -79,11 +79,10 @@ sub run {
}

sub _json {
my $json = Mojo::JSON->new;
return unless my $data = $json->decode(shift);
return unless my $data = j(shift);
return unless defined($data = Mojo::JSON::Pointer->new->get($data, shift));
return _say($data) unless ref $data eq 'HASH' || ref $data eq 'ARRAY';
say $json->encode($data);
say encode_json($data);
}

sub _say { length && say encode('UTF-8', $_) for @_ }
Expand Down
4 changes: 2 additions & 2 deletions lib/Mojolicious/Renderer.pm
Expand Up @@ -3,7 +3,7 @@ use Mojo::Base -base;

use File::Spec::Functions 'catfile';
use Mojo::Cache;
use Mojo::JSON;
use Mojo::JSON 'encode_json';
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util qw(decamelize encode slurp);
Expand All @@ -17,7 +17,7 @@ has handlers => sub {
{
data => sub { ${$_[2]} = $_[3]{data} },
text => sub { ${$_[2]} = $_[3]{text} },
json => sub { ${$_[2]} = Mojo::JSON->new->encode($_[3]{json}) }
json => sub { ${$_[2]} = encode_json($_[3]{json}) }
};
};
has helpers => sub { {} };
Expand Down
6 changes: 3 additions & 3 deletions lib/Mojolicious/Sessions.pm
@@ -1,7 +1,7 @@
package Mojolicious::Sessions;
use Mojo::Base -base;

use Mojo::JSON;
use Mojo::JSON qw(encode_json j);
use Mojo::Util qw(b64_decode b64_encode);

has [qw(cookie_domain secure)];
Expand All @@ -14,7 +14,7 @@ sub load {

return unless my $value = $c->signed_cookie($self->cookie_name);
$value =~ s/-/=/g;
return unless my $session = Mojo::JSON->new->decode(b64_decode $value);
return unless my $session = j(b64_decode $value);

# "expiration" value is inherited
my $expiration = $session->{expiration} // $self->default_expiration;
Expand Down Expand Up @@ -47,7 +47,7 @@ sub store {
$session->{expires} = $default || time + $expiration
if $expiration || $default;

my $value = b64_encode(Mojo::JSON->new->encode($session), '');
my $value = b64_encode(encode_json($session), '');
$value =~ s/=/-/g;
my $options = {
domain => $self->cookie_domain,
Expand Down
5 changes: 2 additions & 3 deletions lib/Test/Mojo.pm
Expand Up @@ -9,7 +9,7 @@ use Mojo::Base -base;
# Bender: You're better off dead, I'm telling you, dude.
# Fry: Santa Claus is gunning you down!"
use Mojo::IOLoop;
use Mojo::JSON;
use Mojo::JSON 'j';
use Mojo::JSON::Pointer;
use Mojo::Server;
use Mojo::UserAgent;
Expand Down Expand Up @@ -288,8 +288,7 @@ sub _build_ok {

sub _json {
my ($self, $method, $p) = @_;
return Mojo::JSON::Pointer->new->$method(
Mojo::JSON->new->decode(@{$self->message // []}[1]), $p);
return Mojo::JSON::Pointer->new->$method(j(@{$self->message // []}[1]), $p);
}

sub _message {
Expand Down
9 changes: 6 additions & 3 deletions t/mojo/json.t
Expand Up @@ -10,7 +10,7 @@ use Mojo::Base -strict;

use Test::More;
use Mojo::ByteStream 'b';
use Mojo::JSON 'j';
use Mojo::JSON qw(decode_json encode_json j);

# Decode array
my $json = Mojo::JSON->new;
Expand Down Expand Up @@ -245,10 +245,10 @@ ok $bytes, 'defined value';
is_deeply $json->decode($bytes), $array, 'successful roundtrip';

# Real world roundtrip
$bytes = $json->encode({foo => 'c:\progra~1\mozill~1\firefox.exe'});
$bytes = encode_json({foo => 'c:\progra~1\mozill~1\firefox.exe'});
is $bytes, '{"foo":"c:\\\\progra~1\\\\mozill~1\\\\firefox.exe"}',
'encode {foo => \'c:\progra~1\mozill~1\firefox.exe\'}';
$hash = $json->decode($bytes);
$hash = decode_json($bytes);
is_deeply $hash, {foo => 'c:\progra~1\mozill~1\firefox.exe'},
'successful roundtrip';

Expand Down Expand Up @@ -369,5 +369,8 @@ is $json->error,
'Malformed JSON: Unexpected data after array at line 3, offset 8',
'right error';
is j('{'), undef, 'decoding failed';
eval { decode_json("[\"foo\",\n\"bar\",\n\"bazra\"]lalala") };
like $@, qr/Malformed JSON: Unexpected data after array at line 3, offset 8/,
'right error';

done_testing();
12 changes: 5 additions & 7 deletions t/mojo/psgi.t
@@ -1,7 +1,7 @@
use Mojo::Base -strict;

use Test::More;
use Mojo::JSON;
use Mojo::JSON 'decode_json';
use Mojo::Server::PSGI;
use Mojolicious::Command::psgi;
use Mojolicious::Lite;
Expand Down Expand Up @@ -60,9 +60,8 @@ while (defined(my $chunk = $res->[2]->getline)) { $params .= $chunk }
is $ENV{MOJO_HELLO}, undef, 'finish event has not been emitted';
$res->[2]->close;
is delete $ENV{MOJO_HELLO}, 'world', 'finish event has been emitted';
$params = Mojo::JSON->new->decode($params);
is_deeply $params, {bar => 'baz', hello => 'world', lalala => 23},
'right structure';
is_deeply decode_json($params),
{bar => 'baz', hello => 'world', lalala => 23}, 'right structure';

# Command
$content = 'world=hello';
Expand Down Expand Up @@ -97,9 +96,8 @@ while (defined(my $chunk = $res->[2]->getline)) { $params .= $chunk }
is $ENV{MOJO_HELLO}, undef, 'finish event has not been emitted';
$res->[2]->close;
is delete $ENV{MOJO_HELLO}, 'world', 'finish event has been emitted';
$params = Mojo::JSON->new->decode($params);
is_deeply $params, {bar => 'baz', world => 'hello', lalala => 23},
'right structure';
is_deeply decode_json($params),
{bar => 'baz', world => 'hello', lalala => 23}, 'right structure';

# Simple
$env = {
Expand Down
4 changes: 2 additions & 2 deletions t/mojo/response.t
Expand Up @@ -5,7 +5,7 @@ use IO::Compress::Gzip 'gzip';
use Mojo::Asset::File;
use Mojo::Content::Single;
use Mojo::Content::MultiPart;
use Mojo::JSON;
use Mojo::JSON 'encode_json';
use Mojo::Message::Response;
use Mojo::Util 'encode';

Expand Down Expand Up @@ -978,7 +978,7 @@ $res = Mojo::Message::Response->new;
$res->parse("HTTP/1.1 200 OK\x0a");
$res->parse("Content-Type: application/json\x0a");
$res->parse("Content-Length: 27\x0a\x0a");
$res->parse(Mojo::JSON->new->encode({foo => 'bar', baz => [1, 2, 3]}));
$res->parse(encode_json({foo => 'bar', baz => [1, 2, 3]}));
ok $res->is_finished, 'response is finished';
is $res->code, 200, 'right status';
is $res->message, 'OK', 'right message';
Expand Down

0 comments on commit 85fe227

Please sign in to comment.