Perl JSON модули с поддержкой UTF-8

Введение

Статья представляет обзор perl модулей для работы с JSON в контексте UTF-8. Подразумевается работа с UTF-8 на уровне символов, а не байтов. Если нужна работа на уровне байт, то подойдет любой из нижеперечисленных моделей, который устроит по скорости.

На CPAN существуют следующие модули:

JSON - parse and convert to JSON (JavaScript Object Notation).

JSON::XS - JSON serialising/deserialising, done correctly and fast

JSON::Syck - JSON is YAML

JSON::PC - fast JSON Parser and Converter (JSON::PC is a XS version of JSON).

JSON::DWIW - JSON converter that Does What I Want

В качестве тестовых данных будем использовать следующий JSON:
{ "hello":"Hello, \u00ab\u043f\u0440\u0438\u0432\u0435\u0442\u00bb \u2116 \u263a. Good by." }
В JSON содержится фраза ``Hello, <<привет>> #. Good by.'', в которой кавычки, номер и смайлик ``уникодные''. Именно как \uxxxx в JSON кодируются UTF-8 символы (http://json.org).

Этому JSON соответствует следующая Perl структура:
{ 'hello' => "Hello, \x{ab}\x{43f}\x{440}\x{438}\x{432}\x{435}\x{442}\x{bb} \x{2116} \x{263a}. Good by." }
Ну что-ж теперь приступим к тесту моделей.

Обзор модулей

JSON.

Для работы у UTF-8 в конструкторе необходимо это указать:
JSON->new(utf8 => 1);
Модуль корректно ставит UTF-8 флаги, и обратном преобразовании кодирует UTF-8 символы. Но есть большое но: модель споткнулся на символах кавычки елочкой.

JSON::PC.

Как указано в документации на модуль, JSON::PC является XS версией модуля JSON, и как истинный приемник наследует туже проблему с русскими или французскими кавычками.

Модуль сам разбирается где UTF-8, ничего явно указывать не нужно.

JSON::Syck.

Модуль JSON::Syck написан сообразительной китаянкой, которая заметила что JSON по сути является YAML, и просто использовала библиотеку libsyck.

UTF-8 не поддерживается вообще, модель ориентирован на работу с октетами, о чем честно написано в документации. Подразумевается \uxxxx, а не ImplicitUnicode опция.

JSON::XS.

А вот модуль JSON::XS показал себя достойно со всех сторон. Правда перед преобразованием из JSON в Perl надо указать флаг utf8, а при обратном преобразовании - флаг ascii.

Из JSON в Perl:

use JSON::XS;
my $json_xs = JSON::XS->new();
$json_xs->utf8(1);
$json_xs->decode($json_data);

Из Perl в JSON:
use JSON::XS;
my $json_xs = JSON::XS->new();
$json_xs->ascii(1);
$json_xs->encode($perl_data);

Можно также задать флаг pretty для красивого форматирования результирующего JSON.

JSON::DWIW.

Последний модуль JSON::DWIW также ведет себе отлично, необходимо лишь в конструкторе сообщить об UTF-8:

use JSON::DWIW;
my $json_dwiw = JSON::DWIW->new({ escape_multi_byte => 1 });
$json_dwiw->from_json($json_data);
$json_dwiw->to_json($perl_data);

Бонусы

Два пакета JSON::XS и JSON::DWIW нормально обрабатывают ситуацию, когда передаваемая им JSON строка не является последовательностью октетов, а является perl строкой с UTF-8 - не надо делать лишнюю проверку.

Benchmark

До этапа тестирования производительности успешно добрались лишь два модуля: JSON::XS и JSON::DWIW, - к которым нет ни одной претензии.

Собственно тест скорости на не слишком большой структуре:

use Benchmark qw(cmpthese);
 cmpthese(10000, {
       'JSON::XS'   => sub {
                       my $json_xs = JSON::XS->new();
                       $json_xs->utf8(1);
                       $json_xs->decode($json_data_u);
                       $json_xs->ascii(1);
                       $json_xs->encode($perl_data_expected)
               },
       'JSON::DWIW' => sub {
                       my $json_obj = JSON::DWIW->new({ escape_multi_byte => 1 });
                       $json_obj->from_json($json_data_u);
                       $json_obj->to_json($perl_data_expected)
               },
});

А вот и результаты:
               Rate JSON::DWIW   JSON::XS
JSON::DWIW  5246/s         --       -78%
JSON::XS   24151/s       360%         --

Вывод

Для меня вывод однозначен --- JSON::XS. Модуль ведет себя отлично с UTF-8, корректно преобразовывает данные в Perl строки и к тому же обладает приличной скоростью.

Оригинал - http://laziness-impatience-hubris.blogspot.com/2008/02/perl-json-utf-8.html

Спасибо за

Спасибо за статью:) Я сам уже давно смотрел на JSON, но все никак не наступает тот переломный момент, когда переходишь с одной технологии на другую.

Кстати, какие выгоды и преимущества ты получил для себя от JSON?

Модуль JSON::Syck

Модуль JSON::Syck написан сообразительной японкой

на самом деле китаянкой http://en.wikipedia.org/wiki/Audrey_Tang, она же является одним из авторов pugs (реализация 6-го перла на хаскеле)

Спасибо, исправил

msa написал
Модуль JSON::Syck написан сообразительной японкой

на самом деле китаянкой http://en.wikipedia.org/wiki/Audrey_Tang, она же является одним из авторов pugs (реализация 6-го перла на хаскеле)

Спасибо, исправил. Забавно, сначала написал китаянкой, потом посмотрел на иероглифы и исправил на японку.

Хм. Так она, кроме Pugs, приложила руку и к SVK и Request Tracker!
Надо брать пример с нее, а то исправил пару раз сборку parrot под FreeBSD и Win32, и все...
А нет... Еще патч для NetBSD протолкнул, и был стимулом немного подправить работу с потоками, хотя NetBSD с тех пор изменилась в этом секторе. А для DragonFly патч потеряли, но паку месяцев назад кто-то другой сделал аналогичный. Вот и все. Капля в море.

И как люди успевают заниматься Open Source?!

YAML

rimas написал
Кстати, какие выгоды и преимущества ты получил для себя от JSON?

Сложно ответить, так как где можно было использовал YAML.

simple JSDumper

Для перевода простых структур из perl-а в js:

my %jskeyword=('do'=>1,'if'=>1,'in'=>1);
sub JSDumper {
    my($v)=@_;
    my $r = ref $v;
    unless($r){
      for($v){
        /\d/ && $v eq $v+0 and return $v;
        s/\\/\\\\/g;
        s/'/\\'/g;
        s/\n/\\n/g;
        s/\r/\\r/g;
        return "'$_'";
      }
    }
    return "[".join(",",map{defined($_)&&JSDumper($_)}@$v)."]" if $r eq 'ARRAY';
    return "{".join(',',map{
        (/\W/||$jskeyword{$_}?JSDumper($_):$_).':'.JSDumper($$v{$_})
    } sort keys %$v)."}" if $r eq 'HASH';
    return "''";
}

Скажите, а зачем парсить обратно js в структуры perl?