Jak uciec przed ciągami znaków w JSON?

154

Podczas ręcznego tworzenia danych JSON, jak zmienić znaczenie pól tekstowych? Należy używać coś jak Apache Commons Langa StringEscapeUtilities.escapeHtml, StringEscapeUtilities.escapeXmlczy powinienem użyć java.net.URLEncoder?

Problem polega na tym, że kiedy używam SEU.escapeHtml, nie unikam cudzysłowów, a kiedy zawijam cały ciąg parą 's, zostanie wygenerowany zniekształcony JSON.

Behrang Saeedzadeh
źródło
20
Jeśli zawijasz cały ciąg w parę ', jesteś skazany na porażkę od samego początku: ciągi JSON można otaczać tylko ". Zobacz ietf.org/rfc/rfc4627.txt .
Thanatos
2
+1 dla StringEscapeUtilitieskonspektu. Jest całkiem przydatne.
Muhammad Gelbana

Odpowiedzi:

157

Najlepiej byłoby znaleźć bibliotekę JSON w swoim języku , do której można wprowadzić odpowiednią strukturę danych, i pozwolić jej martwić się, jak uciec . Dzięki temu będziesz zdrowszy. Jeśli z jakiegoś powodu nie masz biblioteki w swoim języku, nie chcesz jej używać (nie sugerowałbym tego¹) lub piszesz bibliotekę JSON, czytaj dalej.

Ucieknij zgodnie z RFC. JSON jest dość liberalny: Tylko znaki muszą uciec się \, "oraz kody sterujące (coś mniej niż U + 0020).

Ta struktura ucieczki jest specyficzna dla formatu JSON. Będziesz potrzebować funkcji specyficznej dla JSON. Wszystkie znaki ucieczki można zapisać jako \uXXXXgdzie XXXXjest jednostka kodu UTF-16¹ dla tego znaku. Istnieje kilka skrótów, na przykład \\, które również działają. (I skutkują mniejszym i wyraźniejszym wyjściem).

Aby uzyskać szczegółowe informacje, zobacz RFC .

¹JSON's Escaping jest oparty na JS, więc używa \uXXXX, gdzie XXXXjest jednostką kodu UTF-16. W przypadku punktów kodowych poza BMP oznacza to kodowanie par zastępczych, które mogą być nieco owłosione. (Lub możesz po prostu wyprowadzić znak bezpośrednio, ponieważ kod JSON jest kodowany w formacie Unicode i zezwala na te określone znaki).

Thanatos
źródło
Czy w JSON, podobnie jak w JavaScript, można umieszczać ciągi w podwójnych cudzysłowach lub apostrofach? Czy może wystarczy ująć je w podwójne cudzysłowy?
Behrang Saeedzadeh
14
Tylko cudzysłowy ( ").
Thanatos
3
@Sergei: znaki {[]}:?nie mogą być poprzedzane jednym ukośnikiem odwrotnym. ( \:na przykład nie jest prawidłowy w ciągu JSON). Wszystkie z nich można opcjonalnie zmienić za pomocą \uXXXXskładni, marnując kilka bajtów. Zobacz §2.5 RFC.
Thanatos,
2
Nie jestem pewien, jak szeroko jest obsługiwany, ale z mojego doświadczenia wynika, że ​​zadzwoniłam JSON.stringify().
LS
2
@BitTickler znak Unicode nie jest w ogóle niejasny - oznacza po prostu, że ma punkt kodowy (lub punkty) w specyfikacji Unicode. Kiedy używasz std :: string, jest to zbiór znaków Unicode. Kiedy musisz go serializować, powiedzmy do pliku lub przez sieć, to jest miejsce, w którym pojawia się „jakie kodowanie”. Według Thanatosa wydaje się, że chcą, abyś używał UTF, ale technicznie rzecz biorąc, można użyć dowolnego kodowania, o ile można go odtworzyć na znaki Unicode.
Gerard ONeill,
54

Wyciąg z 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();
     }
Mono Threaded
źródło
10
Cóż, to był tag OP
MonoThreaded
Nie rozumiem tylko wtedy, gdy c <'', zmień na \ u. W moim przypadku jest znak \ uD38D, który wynosi 55357 i więcej '', więc nie zmienia się na \ u ...
Kamienny
1
@Stony Brzmi jak nowe pytanie
MonoThreaded
@MonoThreaded Dzięki za odpowiedź, wciąż nie wiem dlaczego. ale w końcu zmieniłem metodę, aby to naprawić jak poniżej, 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, wszystkie znaki inne niż ", \ i znaki kontrolne (te przed „”) są prawidłowe w ciągach JSON, o ile kodowanie wyjściowe jest zgodne. Innymi słowy, nie musisz kodować „펍”, \uD38Do ile zachowane jest kodowanie UTF.
meustrus
37

Spróbuj tego org.codehaus.jettison.json.JSONObject.quote("your string").

Pobierz go tutaj: http://mvnrepository.com/artifact/org.codehaus.jettison/jettison

dpetruha
źródło
Zdecydowanie najlepsze rozwiązanie! Thx
Lastnico
ale to nie cytuje nawiasów klamrowych, takich jak [{
Siergiej
1
@Sergei Nie musisz zmieniać nawiasów klamrowych wewnątrz ciągu JSON.
Yobert
Może być przydatne, aby pokazać, co to faktycznie zwraca.
Trevor
2
org.json.JSONObject.quote ("twój ciąg json") również działa dobrze
webjockey
23

org.json.simple.JSONObject.escape () chroni przed cudzysłowami, \, /, \ r, \ n, \ b, \ f, \ t i innymi znakami sterującymi. Może być używany do ucieczki przed kodami JavaScript.

import org.json.simple.JSONObject;
String test =  JSONObject.escape("your string");
Dan-Dev
źródło
3
To zależy od używanej biblioteki json (JSONObject.escape, JSONObject.quote, ..), ale jest to zawsze statyczna metoda wykonująca zadanie cytowania i po prostu powinna być ponownie wykorzystana
amine
Która biblioteka jest częścią org.json? Nie mam tego na mojej ścieżce klas.
Alex Spurling
22

Apache commons lang teraz to obsługuje. Tylko upewnij się, że masz wystarczająco aktualną wersję Apache commons lang w swojej ścieżce klas. Będziesz potrzebować wersji 3.2+

Uwagi do wydania dla wersji 3.2

LANG-797: Dodano escape / unescapeJson do StringEscapeUtils.

NS du Toit
źródło
To dla mnie najbardziej praktyczna odpowiedź. Większość projektów już używa apache commons lang, więc nie ma potrzeby dodawania zależności dla jednej funkcji. Kreator JSON byłby prawdopodobnie najlepszą odpowiedzią.
absmiths
W ramach kontynuacji, a ponieważ nie mogę dowiedzieć się, jak edytować komentarz, dodałem nowy, znalazłem javax.json.JsonObjectBuilder i javax.json.JsonWriter. Bardzo fajne połączenie konstruktora / pisarza.
absmiths
1
To jest przestarzałe w apache commons lang, musisz użyć apache commons text . Niestety, ta biblioteka jest zgodna z opcjonalną / nieaktualną specyfikacją przez ucieczkę /znaków. To psuje wiele rzeczy, w tym JSON z zawartymi w nim adresami URL. Oryginalna propozycja miała /być specjalną postacią do ucieczki, ale tak już nie jest, jak widać w najnowszej specyfikacji w momencie pisania
adamnfish
10

org.json.JSONObject quote(String data) metoda spełnia swoje zadanie

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

Wyciąg z dokumentacji:

Koduje dane jako ciąg JSON. Dotyczy to cudzysłowów i wszelkich niezbędnych znaków ucieczki . [...] Null zostanie zinterpretowane jako pusty ciąg

IG Pascual
źródło
1
org.apache.sling.commons.json.JSONObjectteż ma to samo
Jordan Shurmer
5

StringEscapeUtils.escapeJavaScript/ też StringEscapeUtils.escapeEcmaScriptpowinien załatwić sprawę.

Hanubindh Krishna
źródło
10
escapeJavaScriptzapisuje pojedyncze cudzysłowy jako \', co jest nieprawidłowe.
Laurt
4

Jeśli używasz fastexml jackson, możesz użyć następujących: com.fasterxml.jackson.core.io.JsonStringEncoder.getInstance().quoteAsString(input)

Jeśli używasz codehaus jackson, możesz użyć: org.codehaus.jackson.io.JsonStringEncoder.getInstance().quoteAsString(input)

Dhiraj
źródło
3

Nie jestem pewien, co masz na myśli, mówiąc „ręczne tworzenie pliku json”, ale możesz użyć czegoś takiego jak gson ( http://code.google.com/p/google-gson/ ), a to przekształci Twoją HashMap, Array, String itp. na wartość JSON. Zalecam wybranie do tego ram.

Vladimir
źródło
2
Mówiąc ręcznie, nie miałem na myśli używania biblioteki JSON, takiej jak Simple JSON, Gson lub XStream.
Behrang Saeedzadeh
To kwestia ciekawostki - dlaczego nie miałbyś chcieć użyć jednego z tych interfejsów API? To tak, jakby próbować ręcznie uciekać przed adresami URL, zamiast używać URLEncode / Decode ...
Vladimir,
1
Niezupełnie to samo, te biblioteki zawierają znacznie więcej niż odpowiednik URLEncode / Decode, zawierają cały pakiet serializacji, aby umożliwić trwałość obiektu Java w formie json, a czasami naprawdę wystarczy zakodować tylko krótką wiązkę tekstu
jmd
2
ręczne tworzenie JSON ma sens, jeśli nie chcesz dołączać biblioteki tylko do serializacji małych fragmentów danych
Aditya Kumar Pandey
2
Chciałbym poprosić członka zespołu o usunięcie z każdego projektu, w którym się znajdowałem, gdyby odważył się stworzyć ręcznie JSON, w którym istnieje biblioteka wysokiej jakości.
Michael Joyce
2

Nie poświęciłem czasu na upewnienie się w 100%, ale zadziałało na tyle, że moje dane wejściowe zostały zaakceptowane przez internetowe walidatory JSON:

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

chociaż nie wygląda lepiej niż org.codehaus.jettison.json.JSONObject.quote("your string")

Po prostu używam już narzędzi dynamicznych w moim projekcie - moje „ręczne budowanie JSON” odbywało się w szablonie szybkości

Tjunkie
źródło
2

Dla tych, którzy przybyli tutaj, szukając rozwiązania wiersza poleceń, jak ja, cURL --data-urlencode działa dobrze:

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

wysyła

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

, na przykład. Większe dane JSON można umieścić w pliku i można użyć składni @, aby określić plik do slurp w danych, z których mają zostać usunięte zmiany znaczenia. Na przykład, jeśli

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

użyłbyś

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

A teraz jest to również samouczek dotyczący wysyłania zapytań do Freebase z wiersza poleceń :-)

vijucat
źródło
2

Użyj klasy EscapeUtils we wspólnym interfejsie API.

EscapeUtils.escapeJavaScript("Your JSON string");
theJ
źródło
1
Zauważ, że na przykład pojedyncze cudzysłowy są traktowane inaczej podczas ucieczki do javascript lub json. W commons.lang 3.4 StringEscapeUtils ( commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/… ) ma metodę escapeJSON, która różni się od metody escapeJavaScript w commons.lang 2: commons.apache. org /
right
1

Rozważmy Moshi „s JsonWriter klasę. Ma cudowne API i ogranicza kopiowanie do minimum, wszystko można ładnie przesyłać strumieniowo do pliku, OutputStream itp.

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

Jeśli chcesz mieć sznurek w dłoni:

Buffer b = new Buffer(); // okio.Buffer
JsonWriter writer = new JsonWriter(b);
//...
String jsonString = b.readUtf8();
orip
źródło
0

Jeśli chcesz pominąć JSON w ciągu JSON, użyj org.json.JSONObject.quote („Twój ciąg json, który wymaga zmiany znaczenia”) wydaje się działać dobrze

webjockey
źródło
0

używając składni \ uXXXX możesz rozwiązać ten problem, google UTF-16 z nazwą znaku, możesz znaleźć XXXX, na przykład: podwójny cudzysłów utf-16

David
źródło
0

Wszystkie metody pokazujące rzeczywistą implementację są wadliwe.
Nie mam kodu Java, ale dla przypomnienia, możesz łatwo przekonwertować ten kod C #:

Dzięki uprzejmości mono-project @ 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();
}

Można to zagęścić w

    // 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
źródło
W jaki quote()sposób metoda opisana w innych odpowiedziach jest wadliwa?
Sandy
0

Myślę, że najlepszą odpowiedzią w 2017 roku jest użycie interfejsów API javax.json. Użyj javax.json.JsonBuilderFactory, aby utworzyć obiekty json, a następnie wypisz obiekty przy użyciu javax.json.JsonWriterFactory. Bardzo fajne połączenie konstruktora / pisarza.

absmyci
źródło