web-dev-qa-db-ja.com

PostgresqlでのUnicodeシーケンスの処理

Postgresqlデータベース(9.4.1)のJSON(JSONBではなく)列に保存されたJSONデータがあります。これらのJSON構造の一部には、属性値にUnicodeシーケンスが含まれています。例えば:

{"client_id": 1, "device_name": "FooBar\ufffd\u0000\ufffd\u000f\ufffd" }

このJSON列を照会しようとすると(device_name属性に直接アクセスしようとしていない場合でも)、次のエラーが表示されます。

エラー:サポートされていないUnicodeエスケープシーケンス
詳細:\u0000はテキストに変換できません。

Postgresqlサーバーで次のコマンドを実行すると、このエラーを再作成できます。

select '{"client_id": 1, "device_name": "FooBar\ufffd\u0000\ufffd\u000f\ufffd" }'::json->>'client_id'

エラーは私には理にかなっています-テキスト結果でUnicodeシーケンスNULLを表す方法はありません。

着信データに対して「衛生」を実行せずに同じJSONデータを照会する方法はありますか?これらのJSON構造は定期的に変更されるため、特定の属性(この場合はdevice_name)をスキャンすることは、同様のデータを保持する他の属性が容易に存在する可能性があるため、良いソリューションではありません。


さらに調査した結果、この動作はバージョン9.4.1で changelogに記載されている として新しくなったようです。

...したがって、エスケープされていないフォームへの変換が必要な場合、\u0000もjson値で拒否されます。この変更は、値に対して処理が行われない限り、json列に\u0000を格納する機能を破壊しません...

これは本当に意図でしたか? 9.4.1以前のバージョンへのダウングレードはここで実行可能なオプションですか?


補足として、このプロパティはクライアントのモバイルデバイスの名前から取得されます。このテキストをデバイスに入力したのはユーザーです。ユーザーはどのように NULL および REPLACEMENT CHARACTER 値を挿入しましたか?!

29
Lix

\u0000は、文字列では無効な1つのUnicodeコードポイントです。文字列をサニタイズする以外に方法はありません。

jsonは特定の形式の単なる文字列であるため、JSON構造を心配することなく、標準の文字列関数を使用できます。コードポイントを削除する1行のサニタイザーは次のようになります。

SELECT (regexp_replace(the_string::text, '\\u0000', '', 'g'))::json;

ただし、好きな文字を挿入することもできます。これは、ゼロコードポイントが何らかの形式の区切り文字として使用される場合に便利です。

また、データベースに保存されるものと、ユーザーに表示される方法との微妙な違いにも注意してください。コードポイントをJSON文字列に保存できますが、値をjsonデータ型として処理する前に、他の文字に前処理する必要があります。

29
Patrick

パトリックによる解決策は、私にとってはすぐにうまくいかなかった。にもかかわらず、常にエラーがスローされました。その後、もう少し調べて、問題を解決する小さなカスタム関数を書くことができました。

まず、次のように書くことでエラーを再現できました。

select json '{ "a":  "null \u0000 escape" }' ->> 'a' as fails

次に、クエリで使用したカスタム関数を追加しました。

CREATE OR REPLACE FUNCTION null_if_invalid_string(json_input JSON, record_id UUID)
  RETURNS JSON AS $$
DECLARE json_value JSON DEFAULT NULL;
BEGIN
  BEGIN
    json_value := json_input ->> 'location';
    EXCEPTION WHEN OTHERS
    THEN
      RAISE NOTICE 'Invalid json value: "%".  Returning NULL.', record_id;
      RETURN NULL;
  END;
  RETURN json_input;
END;
$$ LANGUAGE plpgsql;

関数を呼び出すには、これを行います。エラーは表示されません。

select null_if_invalid_string('{ "a":  "null \u0000 escape" }', id) from my_table

一方、これは予想どおりjsonを返すはずです。

select null_if_invalid_string('{ "a":  "null" }', id) from my_table
0
Hendrik