web-dev-qa-db-fra.com

Gestion des séquences Unicode dans postgresql

J'ai des données JSON stockées dans une colonne JSON (pas JSONB) de ma base de données postgresql (9.4.1). Certaines de ces structures JSON contiennent des séquences unicode dans leurs valeurs d'attribut. Par exemple:

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

Lorsque j'essaie d'interroger cette colonne JSON (même si je n'essaie pas directement d'accéder à l'attribut device_name), j'obtiens le message d'erreur suivant:

ERREUR: séquence d'échappement Unicode non prise en charge
Détail: \u0000 ne peut pas être converti en texte.

Vous pouvez recréer cette erreur en exécutant la commande suivante sur un serveur postgresql:

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

L'erreur a du sens pour moi: il n'y a tout simplement aucun moyen de représenter la séquence unicode NULL dans un résultat textuel.

Puis-je interroger les mêmes données JSON sans avoir à effectuer un "assainissement" des données entrantes? Ces structures JSON changent régulièrement, aussi l'analyse d'un attribut spécifique (device_name dans ce cas) ne serait pas une bonne solution car il pourrait facilement y avoir d'autres attributs pouvant contenir des données similaires.


Après quelques investigations supplémentaires, il semble que ce comportement soit nouveau pour la version 9.4.1 en tant que mentionné dans le journal des modifications :

... Par conséquent, \u0000 sera désormais également rejeté dans les valeurs json lorsque la conversion en formulaire échappé est requise. Cette modification ne rompt pas la possibilité de stocker \u0000 dans les colonnes JSON tant qu'aucun traitement n'est effectué sur les valeurs ...

Était-ce vraiment l'intention? Une mise à niveau antérieure à 9.4.1 est-elle une option viable ici?


En note, cette propriété est tirée du nom du périphérique mobile du client - c’est l’utilisateur qui a saisi ce texte dans le périphérique. Comment diable un utilisateur at-il inséré NULL et REPLACEMENT CHARACTER values?!

26
Lix

\u0000 est le seul point de code Unicode qui n'est pas valide dans une chaîne. Je ne vois pas d'autre moyen que de nettoyer la chaîne.

Comme json est juste une chaîne dans un format spécifique, vous pouvez utiliser les fonctions de chaîne standard sans vous soucier de la structure JSON. Un désinfectant à une ligne pour supprimer le point de code serait:

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

Mais vous pouvez également insérer n'importe quel caractère de votre choix, ce qui serait utile si le point de code zéro est utilisé comme une forme de délimiteur.

Notez également la différence subtile entre ce qui est stocké dans la base de données et sa présentation à l'utilisateur. Vous pouvez stocker le point de code dans une chaîne JSON, mais vous devez le pré-traiter sur un autre caractère avant de traiter la valeur en tant que type de données json.

23
Patrick

La solution de Patrick n'a pas fonctionné pour moi. Peu importe, il y avait toujours une erreur. J'ai ensuite fait un peu plus de recherches et j'ai pu écrire une petite fonction personnalisée qui corrigeait le problème.

Tout d'abord, je pourrais reproduire l'erreur en écrivant:

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

Ensuite, j'ai ajouté une fonction personnalisée que j'ai utilisée dans ma requête:

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;

Pour appeler la fonction, faites ceci. Vous ne devriez pas recevoir d'erreur.

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

Considérant que cela devrait retourner le JSON comme prévu:

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