Comment devrais-je échapper des chaînes dans JSON?

154

Lors de la création manuelle de données JSON, comment dois-je échapper aux champs de chaîne? Dois - je utiliser quelque chose comme Apache Commons Lang StringEscapeUtilities.escapeHtml, StringEscapeUtilities.escapeXmlou devrais - je utiliser java.net.URLEncoder?

Le problème est que lorsque j'utilise SEU.escapeHtml, il n'échappe pas les guillemets et lorsque j'enroule toute la chaîne dans une paire de 's, un JSON mal formé sera généré.

Behrang Saeedzadeh
la source
20
Si vous encapsulez toute la chaîne dans une paire de ', vous êtes condamné dès le début: les chaînes JSON ne peuvent être entourées que de ". Voir ietf.org/rfc/rfc4627.txt .
Thanatos
2
+1 pour le StringEscapeUtilitiescontour. C'est assez utile.
Muhammad Gelbana

Réponses:

157

Idéalement, trouvez une bibliothèque JSON dans votre langage dans laquelle vous pouvez alimenter une structure de données appropriée et laissez-la s'inquiéter de la façon d'échapper aux choses . Cela vous gardera beaucoup plus sain d'esprit. Si pour une raison quelconque vous n'avez pas de bibliothèque dans votre langue, vous ne voulez pas en utiliser une (je ne le suggérerais pas), ou vous écrivez une bibliothèque JSON, lisez la suite.

Échappez-vous selon la RFC. JSON est assez libérale: Les seuls caractères que vous devez échapper sont \, "et les codes de contrôle (rien de moins que U + 0020).

Cette structure d'échappement est spécifique à JSON. Vous aurez besoin d'une fonction spécifique JSON. Tous les échappements peuvent être écrits comme \uXXXXXXXXest l'unité de code UTF-16¹ pour ce caractère. Il existe quelques raccourcis, tels que \\, qui fonctionnent également. (Et ils se traduisent par une sortie plus petite et plus claire.)

Pour plus de détails, consultez la RFC .

L'échappement de ¹JSON est construit sur JS, il utilise donc \uXXXX, où XXXXest une unité de code UTF-16. Pour les points de code en dehors du BMP, cela signifie encoder des paires de substitution, qui peuvent devenir un peu velues. (Ou, vous pouvez simplement afficher le caractère directement, puisque JSON encodé pour est du texte Unicode, et autorise ces caractères particuliers.)

Thanatos
la source
Est-il valide en JSON, comme en JavaScript, de mettre des chaînes entre guillemets doubles ou simples? Ou est-il seulement valide de les mettre entre guillemets?
Behrang Saeedzadeh
14
Seuls les guillemets doubles ( ").
Thanatos
3
@Sergei: Les caractères {[]}:?ne doivent pas être échappés avec une seule barre oblique inverse. ( \:par exemple, n'est pas valide dans une chaîne JSON.) Tous ceux-ci peuvent éventuellement être échappés à l'aide de la \uXXXXsyntaxe, au détriment de plusieurs octets. Voir §2.5 de la RFC.
Thanatos
2
Je ne sais pas dans quelle mesure il est soutenu, mais d'après mon expérience, un appel a JSON.stringify()fait le travail.
LS
2
@BitTickler un caractère unicode n'est pas du tout vague - cela signifie simplement qu'il a un point de code (ou des points) dans la spécification unicode. Lorsque vous utilisez std :: string, c'est un tas de caractères Unicode. Lorsque vous avez besoin de le sérialiser, disons dans un fichier ou sur le réseau, c'est là que «quel encodage» entre en jeu. Il semble selon Thanatos qu'ils veulent que vous utilisiez un UTF, mais techniquement, n'importe quel encodage peut être utilisé tant que il peut être reconstitué en caractères unicode.
Gerard ONeill
54

Extrait de Jettison :

 public static String quote(String string) {
         if (string == null || string.length() == 0) {
             return "\"\"";
         }

         char         c = 0;
         int          i;
         int          len = string.length();
         StringBuilder sb = new StringBuilder(len + 4);
         String       t;

         sb.append('"');
         for (i = 0; i < len; i += 1) {
             c = string.charAt(i);
             switch (c) {
             case '\\':
             case '"':
                 sb.append('\\');
                 sb.append(c);
                 break;
             case '/':
 //                if (b == '<') {
                     sb.append('\\');
 //                }
                 sb.append(c);
                 break;
             case '\b':
                 sb.append("\\b");
                 break;
             case '\t':
                 sb.append("\\t");
                 break;
             case '\n':
                 sb.append("\\n");
                 break;
             case '\f':
                 sb.append("\\f");
                 break;
             case '\r':
                sb.append("\\r");
                break;
             default:
                 if (c < ' ') {
                     t = "000" + Integer.toHexString(c);
                     sb.append("\\u" + t.substring(t.length() - 4));
                 } else {
                     sb.append(c);
                 }
             }
         }
         sb.append('"');
         return sb.toString();
     }
MonoThreaded
la source
10
Eh bien, c'était la balise OP
MonoThreaded
Je ne comprends pas seulement quand c <'', changez en \ u. Dans mon cas, il y a le caractère \ uD38D, qui est 55357 et plus '', donc ne change pas en \ u ...
Stony
1
@Stony Sonne comme une nouvelle question
MonoThreaded
@MonoThreaded Merci pour votre réponse, je ne sais toujours pas pourquoi. mais finalement, j'ai changé la méthode pour la corriger comme ci-dessous, if (c <'' || c> 0x7f) {t = "000" + Integer.toHexString (c) .toUpperCase (); sb.append ("\\ u" + t.substring (t.length () - 4)); } else {sb.append (c); }}
Stony
1
@Stony, tous les caractères autres que ", \ et les caractères de contrôle (ceux précédant «») sont valides dans les chaînes JSON tant que l'encodage de sortie correspond. En d'autres termes, vous n'avez pas besoin d'encoder «펍» tant \uD38Dque l'encodage UTF est conservé.
meustrus
37

Essayez ceci org.codehaus.jettison.json.JSONObject.quote("your string").

Téléchargez-le ici: http://mvnrepository.com/artifact/org.codehaus.jettison/jettison

dpetruha
la source
Certainement la meilleure solution! Thx
Lastnico
mais cela ne cite pas d'accolades comme [{
Sergei
1
@Sergei Vous n'avez pas besoin d'échapper aux accolades à l'intérieur d'une chaîne JSON.
Yobert
Cela peut être utile pour montrer ce que cela renvoie réellement.
Trevor
2
org.json.JSONObject.quote ("votre chaîne json") fonctionne également très bien
webjockey
23

org.json.simple.JSONObject.escape () échappe les guillemets, \, /, \ r, \ n, \ b, \ f, \ t et d'autres caractères de contrôle. Il peut être utilisé pour échapper aux codes JavaScript.

import org.json.simple.JSONObject;
String test =  JSONObject.escape("your string");
Dan-Dev
la source
3
Cela dépend de la bibliothèque json que vous utilisez (JSONObject.escape, JSONObject.quote, ..) mais c'est toujours une méthode statique faisant le travail de citation et devrait simplement être réutilisée
amine
De quelle bibliothèque org.json fait-il partie? Je ne l'ai pas sur mon chemin de classe.
Alex Spurling
22

Apache commons lang prend désormais en charge cela. Assurez-vous simplement d'avoir une version assez récente d'Apache commons lang sur votre chemin de classe. Vous aurez besoin de la version 3.2+

Notes de version pour la version 3.2

LANG-797: Ajout de escape / unescapeJson à StringEscapeUtils.

NS du Toit
la source
C'est la réponse la plus pratique pour moi. La plupart des projets utilisent déjà apache commons lang, donc pas besoin d'ajouter une dépendance pour une fonction. Un constructeur JSON serait probablement la meilleure réponse.
absmiths
En guise de suivi, et comme je ne peux pas comprendre comment modifier un commentaire, j'ai ajouté un nouveau, j'ai trouvé javax.json.JsonObjectBuilder et javax.json.JsonWriter. Très belle combinaison constructeur / écrivain.
absmiths
1
Ceci est obsolète dans apache commons lang, vous devez utiliser le texte apache commons . Malheureusement, cette bibliothèque suit la spécification facultative / obsolète en échappant des /caractères. Cela casse beaucoup de choses, y compris JSON avec des URL. La proposition originale avait /comme caractère spécial pour s'échapper, mais ce n'est plus le cas, comme nous pouvons le voir dans la dernière spécification au moment de la rédaction de cet article
adamnfish
10

org.json.JSONObject quote(String data) la méthode fait le travail

import org.json.JSONObject;
String jsonEncodedString = JSONObject.quote(data);

Extrait de la documentation:

Encode les données sous forme de chaîne JSON. Cela applique les guillemets et tout caractère d'échappement nécessaire . [...] Null sera interprété comme une chaîne vide

IG Pascual
la source
1
org.apache.sling.commons.json.JSONObjecta aussi la même chose
Jordan Shurmer
5

StringEscapeUtils.escapeJavaScript/ StringEscapeUtils.escapeEcmaScriptdevrait faire l'affaire aussi.

Hanubindh Krishna
la source
10
escapeJavaScriptéchappe les guillemets simples comme \', ce qui est incorrect.
laurt
4

Si vous utilisez fastexml jackson, vous pouvez utiliser ce qui suit: com.fasterxml.jackson.core.io.JsonStringEncoder.getInstance().quoteAsString(input)

Si vous utilisez codehaus jackson, vous pouvez utiliser les éléments suivants: org.codehaus.jackson.io.JsonStringEncoder.getInstance().quoteAsString(input)

Dhiraj
la source
3

Je ne sais pas ce que vous entendez par "créer json manuellement", mais vous pouvez utiliser quelque chose comme gson ( http://code.google.com/p/google-gson/ ), et cela transformerait votre HashMap, votre tableau, votre chaîne, etc. , à une valeur JSON. Je recommande d'aller avec un cadre pour cela.

Vladimir
la source
2
Par manuellement, je voulais dire ne pas utiliser une bibliothèque JSON comme Simple JSON, Gson ou XStream.
Behrang Saeedzadeh
Juste une question de curiosité - pourquoi ne voudriez-vous pas utiliser l'une de ces API? C'est comme essayer d'échapper manuellement aux URL, au lieu d'utiliser URLEncode / Decode ...
Vladimir
1
Pas vraiment la même chose, ces bibliothèques viennent avec beaucoup plus que l'équivalent de URLEncode / Decode, elles incluent un package de sérialisation complet pour permettre la persistance de l'objet java sous forme json, et parfois vous n'avez vraiment besoin d'encoder qu'un petit groupe de texte
jmd
2
faire une création manuelle de JSON a du sens, si vous souhaitez ne pas inclure une bibliothèque juste pour sérialiser de petits bits de données
Aditya Kumar Pandey
2
Je demanderais à ce qu'un membre de l'équipe soit supprimé de tout projet sur lequel j'étais s'il osait créer JSON manuellement là où il existait une bibliothèque de haute qualité pour le faire.
Michael Joyce
2

Je n'ai pas passé le temps à m'en assurer à 100%, mais cela a suffisamment fonctionné pour mes entrées pour être accepté par les validateurs JSON en ligne:

org.apache.velocity.tools.generic.EscapeTool.EscapeTool().java("input")

bien que cela ne soit pas meilleur que org.codehaus.jettison.json.JSONObject.quote("your string")

J'utilise déjà simplement des outils de vélocité dans mon projet - ma construction "JSON manuelle" était dans un modèle de vélocité

Tjunkie
la source
2

Pour ceux qui sont venus ici à la recherche d'une solution en ligne de commande, comme moi, --data-urlencode de cURL fonctionne bien:

curl -G -v -s --data-urlencode 'query={"type" : "/music/artist"}' 'https://www.googleapis.com/freebase/v1/mqlread'

envoie

GET /freebase/v1/mqlread?query=%7B%22type%22%20%3A%20%22%2Fmusic%2Fartist%22%7D HTTP/1.1

, par exemple. Des données JSON plus volumineuses peuvent être placées dans un fichier et vous utiliserez la syntaxe @ pour spécifier un fichier à insérer dans les données à échapper. Par exemple, si

$ cat 1.json 
{
  "type": "/music/artist",
  "name": "The Police",
  "album": []
}

vous utiliseriez

curl -G -v -s --data-urlencode query@1.json 'https://www.googleapis.com/freebase/v1/mqlread'

Et maintenant, c'est aussi un tutoriel sur la façon d'interroger Freebase à partir de la ligne de commande :-)

vijucat
la source
2

Utilisez la classe EscapeUtils dans l'API lang commun.

EscapeUtils.escapeJavaScript("Your JSON string");
theJ
la source
1
Notez que les guillemets simples, par exemple, sont traités différemment lors de l'échappement vers javascript ou json. Dans commons.lang 3.4 StringEscapeUtils ( commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/... ) présente un procédé escapeJSON qui est différente de la méthode escapeJavaScript dans commons.lang 2: commons.apache. org / proper / commons-lang / javadocs / api-2.6 / org /…
GlennV
1

Considérez la classe JsonWriter de Moshi . Il a une merveilleuse API et réduit la copie au minimum, tout peut être joliment diffusé dans un fichier, OutputStream, etc.

OutputStream os = ...;
JsonWriter json = new JsonWriter(Okio.buffer(Okio.sink(os)));
json.beginObject();
json.name("id").value(getId());
json.name("scores");
json.beginArray();
for (Double score : getScores()) {
  json.value(score);
}
json.endArray();
json.endObject();

Si vous voulez la chaîne en main:

Buffer b = new Buffer(); // okio.Buffer
JsonWriter writer = new JsonWriter(b);
//...
String jsonString = b.readUtf8();
orip
la source
0

Si vous avez besoin d'échapper au JSON dans la chaîne JSON, utilisez org.json.JSONObject.quote ("votre chaîne json qui doit être échappée") semble bien fonctionner

webjockey
la source
0

en utilisant la syntaxe \ uXXXX peut résoudre ce problème, google UTF-16 avec le nom du signe, vous pouvez trouver XXXX, par exemple: utf-16 guillemet double

David
la source
0

Les méthodes ici qui montrent l'implémentation réelle sont toutes défectueuses.
Je n'ai pas de code Java, mais juste pour mémoire, vous pouvez facilement convertir ce code C #:

Gracieuseté du mono-projet @ https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web/HttpUtility.cs

public static string JavaScriptStringEncode(string value, bool addDoubleQuotes)
{
    if (string.IsNullOrEmpty(value))
        return addDoubleQuotes ? "\"\"" : string.Empty;

    int len = value.Length;
    bool needEncode = false;
    char c;
    for (int i = 0; i < len; i++)
    {
        c = value[i];

        if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92)
        {
            needEncode = true;
            break;
        }
    }

    if (!needEncode)
        return addDoubleQuotes ? "\"" + value + "\"" : value;

    var sb = new System.Text.StringBuilder();
    if (addDoubleQuotes)
        sb.Append('"');

    for (int i = 0; i < len; i++)
    {
        c = value[i];
        if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62)
            sb.AppendFormat("\\u{0:x4}", (int)c);
        else switch ((int)c)
            {
                case 8:
                    sb.Append("\\b");
                    break;

                case 9:
                    sb.Append("\\t");
                    break;

                case 10:
                    sb.Append("\\n");
                    break;

                case 12:
                    sb.Append("\\f");
                    break;

                case 13:
                    sb.Append("\\r");
                    break;

                case 34:
                    sb.Append("\\\"");
                    break;

                case 92:
                    sb.Append("\\\\");
                    break;

                default:
                    sb.Append(c);
                    break;
            }
    }

    if (addDoubleQuotes)
        sb.Append('"');

    return sb.ToString();
}

Cela peut être compacté en

    // https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs
public class SimpleJSON
{

    private static  bool NeedEscape(string src, int i)
    {
        char c = src[i];
        return c < 32 || c == '"' || c == '\\'
            // Broken lead surrogate
            || (c >= '\uD800' && c <= '\uDBFF' &&
                (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF'))
            // Broken tail surrogate
            || (c >= '\uDC00' && c <= '\uDFFF' &&
                (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF'))
            // To produce valid JavaScript
            || c == '\u2028' || c == '\u2029'
            // Escape "</" for <script> tags
            || (c == '/' && i > 0 && src[i - 1] == '<');
    }



    public static string EscapeString(string src)
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();

        int start = 0;
        for (int i = 0; i < src.Length; i++)
            if (NeedEscape(src, i))
            {
                sb.Append(src, start, i - start);
                switch (src[i])
                {
                    case '\b': sb.Append("\\b"); break;
                    case '\f': sb.Append("\\f"); break;
                    case '\n': sb.Append("\\n"); break;
                    case '\r': sb.Append("\\r"); break;
                    case '\t': sb.Append("\\t"); break;
                    case '\"': sb.Append("\\\""); break;
                    case '\\': sb.Append("\\\\"); break;
                    case '/': sb.Append("\\/"); break;
                    default:
                        sb.Append("\\u");
                        sb.Append(((int)src[i]).ToString("x04"));
                        break;
                }
                start = i + 1;
            }
        sb.Append(src, start, src.Length - start);
        return sb.ToString();
    }
}
Stefan Steiger
la source
En quoi la quote()méthode décrite dans les autres réponses est-elle défectueuse?
Sandy
0

Je pense que la meilleure réponse en 2017 est d'utiliser les API javax.json. Utilisez javax.json.JsonBuilderFactory pour créer vos objets json, puis écrivez les objets à l'aide de javax.json.JsonWriterFactory. Très belle combinaison constructeur / écrivain.

absmiths
la source