質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.82%

  • JSON

    969questions

    JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

  • Perl

    427questions

    Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

perl jsonで日本語文字化け

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 95

peter_kes

score 4

Perlで日本語をふくむデータをJson出力すると文字化けが起こります。
色々解決法をためしましたが、直りません。どこがおかしいんでしょうか?

#!/usr/bin/perl

print "Content-Type: text/html; \n\n";

# use module
use Data::Dumper;
use Time::Local;
use DateTime;
use JSON;
use utf8;
use Encode;
use URI::Escape;

use strict;
use warnings;

my $data;
#get data from unity
if ($ENV{'REQUEST_METHOD'} eq 'POST')
{
  read(STDIN, $data, $ENV{'CONTENT_LENGTH'});
} #end of if
else 
{
  $data = $ENV{'QUERY_STRING'};
}#end of else 

my $message;
my $client_number;
(my $message_tag,$message, my $client_number_tag, $client_number) = split(/=|&/,$data);


$message = uri_unescape($message);

my $file_name = "message.json";
if(! -e $file_name) #ファイルがなければ、UTF8文字コードのjsonファイル作成
{
    open my $fh,">", $file_name
    or die "$file_name error $!";
    close $fh;
}

my $dt = DateTime->now( time_zone=>'local');#現時刻取得
my $ymdhms = $dt->datetime;

my $json; #jsonファイルから全てのメッセージデータを取得
{
  local $/; #Enable 'slurp' mode
  open my $fh,"<", $file_name;
  $json = <$fh>;
  close $fh;
}


my @array = ();

if($json){

my $json_in = decode_json($json); ##新しいメッセージを現在のメッセージデータの最後に追加するために存在するデータをあらかじめ配列に入れておく(Jsonファイルに新しいデータを追加するのはどうやったらいいのかわかっていない....)

    foreach my $item( @$json_in) { 
        push @array, $item;

    }
}

#新メッセージ
my $new_data =  {
        'message' => $message,
        'date' => $ymdhms,
        'client_number' => $client_number
    };


    push @array, $new_data;#過去のすべてのメッセージが入っている配列に追加

    my $json_out  = encode_json (\@array); #配列をJson化する

    open my $fh, ">", $file_name
        or die "$file_name error $!";
    print $fh $json_out ;
    close $fh;

    print ("Success");

exit;
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

ascii=>1 がマズいのではないでしょうか。

追記:

ちょっとやってみました。質問文に合ったスクリプトを少し改変し、日本語文字列をスクリプトの中の定数として与え、もう一方でutf8のテキストファイルから読み込み、まとめてJSONで吐き出すスクリプトにしてみました。問題なく出力できました。

#!/usr/bin/perl                                                                                                                                          

use strict;
use utf8;
use JSON;

binmode STDOUT, ":utf8";

my $message="テスト文字列";
my($last_id, $ymdhms, $client_number) = ( "ID0", "170522001600", "1"); # 適当
my @array;

open(my $fhi, "<:utf8", "input_utf8.txt") or die;
open(my $fho, ">:utf8", "output.json")    or die;

while(<$fhi>){
  chomp;
  my $message2=$_;

  my $new_data =  {
    'id'            => $last_id,
    'message'       => $message,
    'message2'      => $message2,
    'date'          => $ymdhms,
    'client_number' => $client_number
  };
  push @array, $new_data;
}

my $json_out  = to_json \@array, {pretty => 1};
print $json_out,"\n"; # コンソールへ
print {$fho} $json_out, "\n"; # ファイルへ

perlで日本語処理する場合の要諦については過去に何度か回答しているのでご覧ください。
https://teratail.com/questions/103103
https://teratail.com/questions/122419
https://teratail.com/questions/105443
だまされたと思って、これらの回答のルールに従って処理し、それ以外の余計なことをやらないようにしてみてください。必ずうまくいくはずです。

個人ブログなどで、生半可な理解のもとにたまたまうまくいった方法を紹介している例がいろいろありますが、ほとんどは混乱のもとになるだけで率直なところ役に立ちません。あまり見ないほうがいいです。

 さらに追記:結局perlで日本語を扱うにはどうすればいいのか総論を書きます

 大原則

  1. 入力されたデータは速やかに「内部コード」化(デコード)されなければならない。
  2. 検索・加工は内部コード化した文字列についてのみ実行されなければならない。
  3. 出力するデータはその寸前に必ず「外部コード」化(エンコード)されなければならない。

「内部コード」とは、正確にはflagged utf8というものらしいが、とりあえずそこは気にしなくていい。

この原則に合致しない形でのエンコード・デコードを生半可な理解のまま絶対試みないこと。

ただし、他のライブラリを通じてデータを受け入れる時、上記に合致しないタイミングでエンコード・デコードされた文字列データを受け取らざるを得ないことがある。この場合は例外的にfrom_to()やdecode(), encode()などの関数を使わなければならないが、通常はこれらの出番はないと思っていい。

ひとたび内部コード化された文字列は、正規表現による検索・置換、substrでの切り出し・置換、その他全ての処理がいわゆる半角文字列と同じ要領で可能となる。utf8では全角文字は3バイトだからなどとややこしいことは一切考える必要なし。

 内部コード化(デコード)の方法

  1. スクリプト内に書かれた文字列(リテラル値) - ここでいうリテラル値とは、たとえば$a="あいうえお"における「あいうえお」の部分を指す。対策: スクリプト冒頭にuse utf8;を置く。
  2. 標準入力からキーボードをたたいて入力される文字列 - 対策: binmode STDIN, ":utf8";を置く。
  3. ファイルから取り込まれる文字列 - 対策: 3パラメータ形式のopen関数を使う。たとえば、open(my $filehandle, "<:utf8", input.txt); 普通にopenしておいて、あとからbinmode $filehandle ":utf8";としてもいいが、回りくどい。

 外部コード化(エンコード)の方法

  1. ファイルへの出力 - 対策: 3パラメータ形式のopen関数を使う。open(my $filehandle, ">:utf8", output.txt);
  2. 標準出力 - 対策: binmode STDOUT, ":utf8";
  3. 標準エラー出力 - 対策: binmode STDERR, ":utf8";

 use utf8についての誤解

この構文が初めて採用されたときの仕様が今日のそれとは違うものだったこともあり、いまだに不正確な情報が独り歩きしているようだが、くれぐれもややこしく考えないこと。

use utf8は「当該スクリプト中の文字列は内部コード化して取り扱え」と指示するもので、対ファイル・コンソールの入出力には関係しない。つまりuse utf8したからファイルのデコードは必要ないとかエンコードは必要ないとか、そういう議論は全部間違っている。

スクリプト中にマルチバイト文字列(漢字カタカナヒラガナその他いわゆる全角文字)を書かないならば、use utf8を書く必要はない(書いてもとくに支障はないが)。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/21 20:06

    御回答ありがとうございます。
    my $json_out = to_json \@array, {utf8=>1, pretty => 1};
    などにしてみたり、ascii=>1部分無しにしてみても文字化けしてしまいます。

    キャンセル

  • 2018/05/21 23:50

    print $messageとしたときは正常に表示できますか?

    キャンセル

  • 2018/05/22 17:14

    わあ!!コードありがとうございます!! すごく勉強になります!!!!

    この$message なんですが、
    <unity側C#>
    WWWForm www = new WWWForm ();
    www.AddField ("messsage", ”日本語");
    WWW result = new WWW (url, www);
    yield return result;
    <CGI側>
    my $data;
    #get data from unity
    if ($ENV{'REQUEST_METHOD'} eq 'POST')
    {
    read(STDIN, $data, $ENV{'CONTENT_LENGTH'});
    } #end of if
    else
    {
    $data = $ENV{'QUERY_STRING'};
    }#end of else

    my $message;
    (my $message_tag,$message) = split(/=|&/,$data);

    print $message でこの時点で既に文字化け状態%e3%81%82%e3 になっていることが判明しました!!
    wwwで渡している時点ですでに文字化けしていることになります。書いていただいたコードと結果のとおり、Jsonの問題ではないようです。
    重要なヒントいただきありがとうございます!!

    キャンセル

  • 2018/05/22 21:42

    %82%e3の文字化けはuri_unescapeで解決しました。
    これはチャットプログラムなんですが、
    1つ目のメッセージは文字化けせずに保存されますが、1つ目以降アペンドする形でメッセージを追加すると文字化けになります。ファイルの読み込み書き込み部分に>:encoding(UTF-8)
    <:encoding(UTF-8) どちらもいれるか入れないか色々パターンを試してみましたが、1つ目がダメになるパターン、3つ目からだめになるなど色々なパターンの文字化けがどうしてもおこってしまいます。内部コードについてもようやく意味がわかってきたのですが、use utf8;をいれておけばいいということではないようです。なにがまちがっているんでしょうか?

    キャンセル

  • 2018/05/23 04:41

    問題が入力元なのか入力後の操作なのか出力時の問題なのかをはっきりさせましょう。まずwhile(<>)の直後でprint $_;してみて正常な出力が得られるか見ましょう。
    気になる点としては、
    (1) encode_jsonを使うなら、出力用ハンドルのopenで">:utf8"を指定してはいけません。encodeしたものをさらにencodeすることになってしまいます。
    (2) print "Content-Type: text/html; charset=Shift_JIS\n\n"; これは明らかにダメです。
    (3) 「一つ目のメッセージ」と「アペンドする形」がどう違うのか、質問文のコードを見ても理解できませんでした。いずれにせよ、データの流れを変に複雑化させると、decode/encodeが出来てなかったり逆に2回やってしまったりということをやらかしてしまいますので、できるだけシンプル化する方向で見直したほうが良いでしょう。

    キャンセル

  • 2018/05/23 15:34

    御回答ありがとうございます。
    (1)やはり2重エンコーディングになっているのですね。
    (2)これはprint "Content-Type: text/html; charset=uft8\n\n が正しいいでしょうか?
    (3) はい、多重文字化けの流れがjsonでの文字化けの問題もあいまってわからないのでまずjsonにしないでtxtで保存して試してみます。

    キャンセル

  • 2018/05/24 16:03

    use utf8は「当該スクリプト中の文字列は内部コード化して取り扱え」と指示する これが鉄則ですね!! 
    外部へのファイルの書き出しで、3パラメータ形式のopen関数open(my $filehandle, "<:utf8", input.txt); を使ったり標準出力でbinmode STDOUT, ":utf8と明記したり、インプットアウトプットする時もしっかり明記しないといけない点もわかりました!!
    ここまでわかりやすくまとめていただき、すごく助かりました!! たぶんこれで忘れません。
    本当にありがとうございました!(^^)!

    キャンセル

0

#!/usr/bin/perl

print "Content-Type: text/html; \n\n";

# use module
use Data::Dumper;
use Time::Local;
use DateTime;
use JSON;
use utf8;
use Encode;

use strict;
use warnings;

use URI::Escape;

my $data;
#get data from unity
if ($ENV{'REQUEST_METHOD'} eq 'POST')
{
  read(STDIN, $data, $ENV{'CONTENT_LENGTH'});
} #end of if
else 
{
  $data = $ENV{'QUERY_STRING'};
}#end of else 

my $message;
my $client_number;
(my $message_tag,$message, my $client_number_tag, $client_number) = split(/=|&/,$data);

$message = uri_unescape($message);

    my $file_name = "message.json";
    if(! -e $file_name) 
    {
        open my $fh,">", $file_name
        or die "$file_name error $!";
        close $fh;
    }

    my $dt = DateTime->now( time_zone=>'local');
    my $ymdhms = $dt->datetime;


my $json;
{
  local $/; #Enable 'slurp' mode
  open my $fh,"<", $file_name;
  $json = <$fh>;
  close $fh;
}

my $last_id = 0;

my @array = ();

if($json){

        my $json_in = from_json($json);

        foreach my $item( @$json_in) { 
            print Dumper($item);
            push @array, $item;
            $last_id = $item->{id};
        }
    }

    $last_id =  $last_id + 1;

    my $new_data =  {
        'id' => $last_id,
        'message' => $message,
        'date' => $ymdhms,
        'client_number' => $client_number
    };

    push @array, $new_data;

    my $json_out  = to_json \@array, { pretty => 1}; 

    open my $fh, ">", $file_name
        or die "$file_name error $!";
    print $fh $json_out ;
    close $fh;

    print ("Success");

exit;


これが最終的に文字化けが解決したコードになります。
解決方法は以下の様になります。
1.回答者さんが教えてくれたリンクでperlの日本語文字の扱い、エンコードデコード内部データ化について学習する
2.%cd%cdaf が出たら、 URI::Escapeを使ってアンエスケープする。
3.perlのファイルはuft8に最初からしておく初級学習サイトでおなじないとかよく書いてありますが、print "Content-Type: text/html; charset=Shift_JIS\n\n はいらない。
4.use utf8;をいれて、ファイルの読み込み書き込みに"<:encoding(UTF-8)"と記述しない。(多重エンコードになってしまう)
5.use utf8;をいれると eocode_json decode_json ではなく(多重エンコードになる 参照http://orange-factory.com/dnf/perlmodule_json.html ) to_json from_json を使用すること
同じ問題が起こったかたがいれば参考にしてください。
御回答者様、沢山ヒントを出していただきありがとうございました。すごく助かりました。 

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/23 18:53

    残念ですがそのuse utf8の理解は間違っています。 のちほどまとめて追記します。

    キャンセル

  • 2018/05/23 20:53

    そうですか(;゚д゚)アッ 追記助かります!!   お時間ございましたらで大丈夫ですので、ほかの見る人が同じく間違った情報で理解しないためによろしければ追記していただけたらと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.82%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • JSON

    969questions

    JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

  • Perl

    427questions

    Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。