Wie soll ich Strings in JSON entkommen?

154

Wie soll ich beim manuellen Erstellen von JSON-Daten Zeichenfolgenfelder umgehen? Sollte ich so etwas wie Apache Commons Lang StringEscapeUtilities.escapeHtml, StringEscapeUtilities.escapeXmloder soll ich verwenden java.net.URLEncoder?

Das Problem ist, dass bei der Verwendung SEU.escapeHtmlkeine Anführungszeichen entstehen und wenn ich die gesamte Zeichenfolge in ein Paar von 's einbinde, wird ein fehlerhafter JSON generiert.

Behrang Saeedzadeh
quelle
20
Wenn Sie die gesamte Zeichenfolge in ein Paar 'einschließen, sind Sie von Anfang an zum Scheitern verurteilt: JSON-Zeichenfolgen können nur mit umgeben werden ". Siehe ietf.org/rfc/rfc4627.txt .
Thanatos
2
+1 für die StringEscapeUtilitiesGliederung. Es ist ziemlich nützlich.
Muhammad Gelbana

Antworten:

157

Suchen Sie im Idealfall eine JSON-Bibliothek in Ihrer Sprache , der Sie eine geeignete Datenstruktur zuführen können, und lassen Sie sie sich Gedanken darüber machen, wie Sie den Dingen entkommen können . Es wird dich viel vernünftiger halten. Wenn Sie aus irgendeinem Grund keine Bibliothek in Ihrer Sprache haben, keine verwenden möchten (ich würde dies nicht vorschlagen¹) oder eine JSON-Bibliothek schreiben, lesen Sie weiter.

Entkomme es gemäß dem RFC. JSON ist ziemlich liberal: Die einzigen Zeichen , die Sie müssen sind entkommen \, "und Tonfolgen (etwas weniger als U + 0020).

Diese Escape-Struktur ist spezifisch für JSON. Sie benötigen eine JSON-spezifische Funktion. Alle Escapezeichen können so geschrieben werden, \uXXXXdass XXXXsich die UTF-16-Codeeinheit¹ für dieses Zeichen befindet. Es gibt einige Verknüpfungen, z. B. \\, die ebenfalls funktionieren. (Und sie führen zu einer kleineren und klareren Ausgabe.)

Ausführliche Informationen finden Sie im RFC .

¹JSON der entweichende auf JS gebaut, so verwendet sie \uXXXX, wo XXXXist ein UTF-16 - Code - Einheit. Für Codepunkte außerhalb des BMP bedeutet dies, dass Ersatzpaare codiert werden, die etwas haarig werden können. (Oder Sie können das Zeichen einfach direkt ausgeben, da JSON für Unicode-Text codiert ist und diese bestimmten Zeichen zulässt.)

Thanatos
quelle
Ist es in JSON wie in JavaScript gültig, Zeichenfolgen in doppelte oder einfache Anführungszeichen zu setzen? Oder ist es nur gültig, sie in doppelte Anführungszeichen zu setzen?
Behrang Saeedzadeh
14
Nur doppelte Anführungszeichen ( ").
Thanatos
3
@Sergei: Die Zeichen {[]}:?dürfen nicht mit einem einzigen Backslash maskiert werden. ( \:Ist beispielsweise in einer JSON-Zeichenfolge nicht gültig.) Alle diese Zeichen können optional mithilfe der \uXXXXSyntax maskiert werden, wobei mehrere Bytes verschwendet werden. Siehe §2.5 des RFC.
Thanatos
2
Ich bin mir nicht sicher, wie weit es unterstützt wird, aber meiner Erfahrung nach hat ein Aufruf, JSON.stringify()den Job zu erledigen.
LS
2
@BitTickler Ein Unicode-Zeichen ist überhaupt nicht vage - es bedeutet nur, dass es einen Codepunkt (oder Punkte) in der Unicode-Spezifikation hat. Wenn Sie std :: string verwenden, handelt es sich um eine Reihe von Unicode-Zeichen. Wenn Sie es serialisieren müssen, sagen wir zu einer Datei oder über das Netzwerk, kommt hier "welche Codierung" ins Spiel. Laut Thanatos möchten sie, dass Sie eine UTF verwenden, aber technisch kann jede Codierung verwendet werden, solange Es kann in Unicode-Zeichen rekonstituiert werden.
Gerard ONeill
54

Auszug aus 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
quelle
10
Nun, das war das OP-Tag
MonoThreaded
Verstehe nicht nur, wenn c <'', wechsle zu \ u. In meinem Fall gibt es das Zeichen \ uD38D, das 55357 und älter ist '', ändert sich also nicht zu \ u ...
Stony
1
@ Tony Klingt nach einer neuen Frage
MonoThreaded
@MonoThreaded Danke für deine Antwort, ich weiß immer noch nicht warum. aber schließlich habe ich die Methode geändert, um sie wie folgt zu beheben, wenn (c <'' || c> 0x7f) {t = "000" + Integer.toHexString (c) .toUpperCase (); sb.append ("\\ u" + t.substring (t.length () - 4)); } else {sb.append (c); }}
Stony
1
@Stony, alle anderen Zeichen als ", \ und Steuerzeichen (die vor "") sind in JSON-Zeichenfolgen gültig, solange die Ausgabecodierung übereinstimmt. Mit anderen Worten, Sie müssen "펍" nicht codieren, \uD38Dsolange die UTF-Codierung erhalten bleibt.
Meustrus
37

Versuchen Sie dies org.codehaus.jettison.json.JSONObject.quote("your string").

Laden Sie es hier herunter: http://mvnrepository.com/artifact/org.codehaus.jettison/jettison

dpetruha
quelle
Auf jeden Fall die beste Lösung! Thx
Lastnico
aber dies zitiert keine Klammern wie [{
Sergei
1
@Sergei Sie müssen Klammern innerhalb einer JSON-Zeichenfolge nicht maskieren.
Yobert
Könnte nützlich sein, um zu zeigen, was dies tatsächlich zurückgibt.
Trevor
2
org.json.JSONObject.quote ("Ihre json Zeichenfolge") funktioniert auch gut
Webjockey
23

org.json.simple.JSONObject.escape () maskiert Anführungszeichen, \, /, \ r, \ n, \ b, \ f, \ t und andere Steuerzeichen. Es kann verwendet werden, um JavaScript-Codes zu umgehen.

import org.json.simple.JSONObject;
String test =  JSONObject.escape("your string");
Dan-Dev
quelle
3
Es hängt von der von Ihnen verwendeten JSON-Bibliothek ab (JSONObject.escape, JSONObject.quote, ..), aber es ist immer eine statische Methode, die den Angebotsjob ausführt, und sollte einfach wiederverwendet werden
amine
Zu welcher Bibliothek gehört org.json? Ich habe es nicht auf meinem Klassenweg.
Alex Spurling
22

Apache Commons Lang unterstützt dies jetzt. Stellen Sie einfach sicher, dass Sie eine ausreichend aktuelle Version von Apache commons lang in Ihrem Klassenpfad haben. Sie benötigen Version 3.2+

Versionshinweise für Version 3.2

LANG-797: Escape / unescapeJson zu StringEscapeUtils hinzugefügt.

NS du Toit
quelle
Dies ist die praktischste Antwort für mich. Die meisten Projekte verwenden bereits apache commons lang, sodass keine Abhängigkeit für eine Funktion hinzugefügt werden muss. Ein JSON-Builder wäre wahrscheinlich die beste Antwort.
Absmiths
Als Folge habe ich javax.json.JsonObjectBuilder und javax.json.JsonWriter gefunden, da ich nicht herausfinden kann, wie ein Kommentar bearbeitet werden soll. Ich habe einen neuen hinzugefügt. Sehr schöne Builder / Writer-Kombination.
Absmiths
1
Dies ist in Apache Commons Lang veraltet. Sie müssen Apache Commons Text verwenden . Leider folgt diese Bibliothek der optionalen / veralteten Spezifikation, indem /Zeichen maskiert werden . Dies zerstört viele Dinge, einschließlich JSON mit URLs. Der ursprüngliche Vorschlag hatte /als Sonderzeichen zu entkommen, aber dies ist nicht mehr der Fall, wie wir in der neuesten Spezifikation zum Zeitpunkt des Schreibens sehen können
Adamnfish
10

org.json.JSONObject quote(String data) Methode macht den Job

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

Auszug aus der Dokumentation:

Codiert Daten als JSON-Zeichenfolge. Dies gilt für Anführungszeichen und alle erforderlichen Zeichen, die entkommen . [...] Null wird als leere Zeichenfolge interpretiert

IG Pascual
quelle
1
org.apache.sling.commons.json.JSONObjecthat auch das gleiche
Jordan Shurmer
5

StringEscapeUtils.escapeJavaScriptIch StringEscapeUtils.escapeEcmaScriptsollte den Trick auch machen.

Hanubindh Krishna
quelle
10
escapeJavaScriptentgeht einfachen Anführungszeichen als \', was falsch ist.
Laurt
4

Wenn Sie fastexml jackson verwenden, können Sie Folgendes verwenden: com.fasterxml.jackson.core.io.JsonStringEncoder.getInstance().quoteAsString(input)

Wenn Sie Codehaus Jackson verwenden, können Sie Folgendes verwenden: org.codehaus.jackson.io.JsonStringEncoder.getInstance().quoteAsString(input)

Dhiraj
quelle
3

Sie sind sich nicht sicher, was Sie unter "manuell erstellen von json" verstehen, aber Sie können etwas wie gson ( http://code.google.com/p/google-gson/ ) verwenden, das Ihre HashMap, Ihr Array, Ihren String usw. Transformieren würde auf einen JSON-Wert. Ich empfehle dafür einen Rahmen.

Vladimir
quelle
2
Mit manuell meinte ich nicht die Verwendung einer JSON-Bibliothek wie Simple JSON, Gson oder XStream.
Behrang Saeedzadeh
Nur eine Frage der Neugier - warum sollten Sie keine dieser APIs verwenden? Es ist, als würde man versuchen, URLs manuell zu umgehen, anstatt URLEncode / Decode zu verwenden ...
Vladimir
1
Nicht wirklich die gleichen, kommen diese Bibliotheken mit viel mehr als das Äquivalent von URLEncode / Decode, sie umfassen eine ganze Serialisierung Paket Persistenz von Java - Objekt in json Form zu ermöglichen, und manchmal Ihr wirklich nur eine kurze Reihe von Text kodieren müssen
jmd
2
Eine manuelle Erstellung von JSON ist sinnvoll, wenn Sie keine Bibliothek nur zum Serialisieren kleiner Datenmengen
hinzufügen
2
Ich würde darum bitten, dass ein Teammitglied aus jedem Projekt entfernt wird, an dem ich beteiligt bin, wenn es sich traut, JSON manuell zu erstellen, wenn es dafür eine qualitativ hochwertige Bibliothek gibt.
Michael Joyce
2

Ich habe nicht die Zeit aufgewendet, um 100% sicher zu stellen, aber es hat für meine Eingaben genug funktioniert, um von Online-JSON-Validatoren akzeptiert zu werden:

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

obwohl es nicht besser aussieht als org.codehaus.jettison.json.JSONObject.quote("your string")

Ich verwende in meinem Projekt einfach bereits Geschwindigkeitswerkzeuge - mein "manuelles JSON" -Gebäude befand sich in einer Geschwindigkeitsvorlage

Tjunkie
quelle
2

Für diejenigen, die hierher gekommen sind, um nach einer Befehlszeilenlösung zu suchen, wie ich, funktioniert der --data-urlencode von cURL einwandfrei:

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

sendet

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

, zum Beispiel. Größere JSON-Daten können in eine Datei eingefügt werden, und Sie würden die @ -Syntax verwenden, um eine Datei anzugeben, aus der die zu maskierenden Daten verschlungen werden sollen. Zum Beispiel wenn

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

du würdest verwenden

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

Und jetzt ist dies auch ein Tutorial, wie man Freebase über die Kommandozeile abfragt :-)

Vijucat
quelle
2

Verwenden Sie die EscapeUtils-Klasse in der Commons-Lang-API.

EscapeUtils.escapeJavaScript("Your JSON string");
theJ
quelle
1
Beachten Sie, dass einfache Anführungszeichen beispielsweise anders behandelt werden, wenn Sie zu Javascript oder JSON wechseln. In commons.lang 3.4 hat StringEscapeUtils ( commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/… ) eine EscapeJSON-Methode, die sich von der EscapeJavaScript-Methode in commons.lang 2: commons.apache unterscheidet. org / richtig / commons-lang / javadocs / api-2.6 / org /…
GlennV
1

Betrachten Sie Moshi ‚s JsonWriter Klasse. Es hat eine wunderbare API und reduziert das Kopieren auf ein Minimum. Alles kann gut in eine Datei, einen OutputStream usw. gestreamt werden.

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();

Wenn Sie die Zeichenfolge in der Hand haben möchten:

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

Wenn Sie JSON innerhalb der JSON-Zeichenfolge maskieren müssen, scheint die Verwendung von org.json.JSONObject.quote ("Ihre json-Zeichenfolge, die maskiert werden muss") gut zu funktionieren

Webjockey
quelle
0

Mit der \ uXXXX-Syntax kann dieses Problem gelöst werden. Google UTF-16 mit dem Namen des Zeichens. Sie können XXXX herausfinden, zum Beispiel: utf-16 doppeltes Anführungszeichen

David
quelle
0

Die Methoden hier, die die tatsächliche Implementierung zeigen, sind alle fehlerhaft.
Ich habe keinen Java-Code, aber nur für den Datensatz können Sie diesen C # -Code leicht konvertieren:

Mit freundlicher Genehmigung des Monoprojekts @ 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();
}

Dies kann verdichtet werden

    // 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
quelle
Wie ist die quote()in anderen Antworten beschriebene Methode fehlerhaft?
Sandy
0

Ich denke, die beste Antwort im Jahr 2017 ist die Verwendung der javax.json-APIs. Verwenden Sie javax.json.JsonBuilderFactory, um Ihre json-Objekte zu erstellen, und schreiben Sie die Objekte dann mit javax.json.JsonWriterFactory aus. Sehr schöne Builder / Writer-Kombination.

Absmiths
quelle