¿Cómo debo escapar de cadenas en JSON?

154

Al crear datos JSON manualmente, ¿cómo debo escapar de los campos de cadena? ¿Debo usar algo así como de Apache Commons Lang StringEscapeUtilities.escapeHtml, StringEscapeUtilities.escapeXmlo debo usar java.net.URLEncoder?

El problema es que cuando lo uso SEU.escapeHtml, no escapa a las comillas y cuando envuelvo toda la cadena en un par de 's, se generará un JSON con formato incorrecto.

Behrang Saeedzadeh
fuente
20
Si está envolviendo toda la cadena en un par de ', está condenado desde el principio: las cadenas JSON solo se pueden rodear ". Ver ietf.org/rfc/rfc4627.txt .
Thanatos
2
+1 para el StringEscapeUtilitiesesquema. Es muy útil.
Muhammad Gelbana

Respuestas:

157

Idealmente, encuentre una biblioteca JSON en su idioma en la que pueda alimentar una estructura de datos adecuada y deje que se preocupe por cómo escapar de las cosas . Te mantendrá mucho más sano. Si por alguna razón no tiene una biblioteca en su idioma, no quiere usar una (no sugeriría esto¹), o está escribiendo una biblioteca JSON, siga leyendo.

Escápelo de acuerdo con la RFC. JSON es bastante liberal: Los únicos caracteres que debe escapar son \, "y los códigos de control (nada menos que U + 0020).

Esta estructura de escape es específica de JSON. Necesitará una función específica de JSON. Todos los escapes se pueden escribir como \uXXXXdónde XXXXestá la unidad de código UTF-16 para ese carácter. Hay algunos atajos, como por ejemplo \\, que también funcionan. (Y dan como resultado una salida más pequeña y más clara).

Para más detalles, vea el RFC .

El escape de JSON se basa en JS, por lo que utiliza \uXXXX, donde XXXXestá una unidad de código UTF-16. Para puntos de código fuera del BMP, esto significa codificar pares sustitutos, lo que puede ser un poco complicado. (O bien, puede simplemente generar el carácter directamente, ya que JSON está codificado para texto Unicode y permite estos caracteres en particular).

Thanatos
fuente
¿Es válido en JSON, como en JavaScript, encerrar cadenas entre comillas dobles o comillas simples? ¿O solo es válido encerrarlos entre comillas dobles?
Behrang Saeedzadeh
14
Solo comillas dobles ( ").
Thanatos
3
@Sergei: los personajes {[]}:?no se deben escapar con una barra invertida simple. ( \:por ejemplo, no es válido en una cadena JSON). Todos estos pueden escaparse opcionalmente utilizando la \uXXXXsintaxis, con el desperdicio de varios bytes. Ver §2.5 del RFC.
Thanatos
2
No estoy seguro de cuán ampliamente es compatible, pero en mi experiencia una llamada para JSON.stringify()hacer el trabajo.
LS
2
@BitTickler, un carácter unicode no es nada vago, solo significa que tiene un punto de código (o puntos) en la especificación unicode. Cuando usa std :: string, es un montón de caracteres unicode. Cuando necesite serializarlo, digamos a un archivo oa través de la red, ahí es donde entra 'qué codificación'. Según Thanatos, parece que quieren que use un UTF, pero técnicamente cualquier codificación puede usarse siempre que Se puede reconstituir en caracteres Unicode.
Gerard ONeill
54

Extracto 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();
     }
Monohilo
fuente
10
Bueno, esta fue la etiqueta OP
MonoThreaded
No entiendo solo cuando c <'', cambie a \ u. En mi caso, hay un carácter \ uD38D, que es 55357 y más '', por lo que no cambia a \ u ...
Stony
1
@Stony Suena como una nueva pregunta
MonoThreaded
@MonoThreaded Gracias por su respuesta, todavía no sé por qué. pero finalmente, cambié el método para solucionarlo como a continuación, 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, todos los caracteres que no sean ", \ y los caracteres de control (los anteriores a "") son válidos dentro de las cadenas JSON siempre que la codificación de salida coincida. En otras palabras, no necesita codificar "펍" mientras \uD38Dse conserve la codificación UTF.
meustrus
37

Intenta esto org.codehaus.jettison.json.JSONObject.quote("your string").

Descárguelo aquí: http://mvnrepository.com/artifact/org.codehaus.jettison/jettison

dpetruha
fuente
¡Definitivamente la mejor solución! Thx
Lastnico
pero esto no hace referencia a llaves como [{
Sergei
1
@Sergei No tiene que escapar de llaves dentro de una cadena JSON.
Yobert
Puede ser útil para mostrar lo que esto realmente devuelve.
Trevor
2
org.json.JSONObject.quote ("su cadena json") también funciona bien
webjockey
23

org.json.simple.JSONObject.escape () escapa entre comillas, \, /, \ r, \ n, \ b, \ f, \ ty otros caracteres de control. Se puede usar para escapar de los códigos JavaScript.

import org.json.simple.JSONObject;
String test =  JSONObject.escape("your string");
Dan-Dev
fuente
3
Depende de la biblioteca JSON que está utilizando (JSONObject.escape, JSONObject.quote, ..), pero siempre es un método estático hacer el trabajo y simplemente citando debe reutilizarse
amina
¿De qué biblioteca forma parte org.json? No lo tengo en mi classpath.
Alex Spurling
22

Apache commons lang ahora es compatible con esto. Solo asegúrese de tener una versión suficientemente reciente de Apache commons lang en su classpath. Necesitarás la versión 3.2+

Notas de la versión para la versión 3.2

LANG-797: Se agregó escape / unescapeJson a StringEscapeUtils.

NS du Toit
fuente
Esta es la respuesta más práctica para mí. La mayoría de los proyectos ya usan apache commons lang, por lo que no es necesario agregar una dependencia para una función. Un constructor JSON probablemente sería la mejor respuesta.
Herreros
Como seguimiento, y debido a que no puedo entender cómo editar un comentario, agregué uno nuevo, encontré javax.json.JsonObjectBuilder y javax.json.JsonWriter. Muy buena combinación de constructor / escritor.
Herreros
1
Esto está en desuso en apache commons lang, debe usar el texto de apache commons . Lamentablemente, esta biblioteca sigue las especificaciones opcionales / desactualizadas al escapar /caracteres. Esto rompe muchas cosas, incluido JSON con URL en él. La propuesta original tenía /un carácter especial para escapar, pero este ya no es el caso, como podemos ver en la última especificación al momento de escribir
adamnfish el
10

org.json.JSONObject quote(String data) método hace el trabajo

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

Extracto de la documentación:

Codifica los datos como una cadena JSON. Esto aplica comillas y cualquier escape de caracteres necesario . [...] Null se interpretará como una cadena vacía

IG Pascual
fuente
1
org.apache.sling.commons.json.JSONObjecttambién tiene esta misma cosa
Jordan Shurmer el
5

StringEscapeUtils.escapeJavaScript/ StringEscapeUtils.escapeEcmaScriptDebe hacer el truco también.

Hanubindh Krishna
fuente
10
escapeJavaScriptescapa a comillas simples como \', lo cual es incorrecto.
laurt
4

Si está usando fastexml jackson, puede usar lo siguiente: com.fasterxml.jackson.core.io.JsonStringEncoder.getInstance().quoteAsString(input)

Si está usando codehaus jackson, puede usar lo siguiente: org.codehaus.jackson.io.JsonStringEncoder.getInstance().quoteAsString(input)

Dhiraj
fuente
3

No estoy seguro de lo que quieres decir con "crear json manualmente", pero puedes usar algo como gson ( http://code.google.com/p/google-gson/ ), y eso transformaría tu HashMap, Array, String, etc. , a un valor JSON. Recomiendo ir con un marco para esto.

Vladimir
fuente
2
Al decir manualmente no quise usar una biblioteca JSON como Simple JSON, Gson o XStream.
Behrang Saeedzadeh
Solo por curiosidad: ¿por qué no querrías usar una de estas API? Es como tratar de escapar de las URL manualmente, en lugar de usar URLEncode / Decode ...
Vladimir
1
No es lo mismo, esas bibliotecas vienen con mucho más que el equivalente de URLEncode / Decode, incluyen un paquete de serialización completo para permitir la persistencia del objeto java en forma json, y a veces solo necesitas codificar un pequeño grupo de texto
jmd
2
hacer una creación manual de JSON tiene sentido, si no desea incluir una biblioteca solo para serializar pequeños fragmentos de datos
Aditya Kumar Pandey
2
Solicitaría que se elimine a un miembro del equipo de cualquier proyecto en el que estuviese si se atrevieran a crear JSON manualmente donde existiera una biblioteca de alta calidad para hacerlo.
Michael Joyce
2

No he pasado el tiempo para asegurarme al 100%, pero funcionó para mis entradas lo suficiente como para ser aceptado por los validadores JSON en línea:

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

aunque no se ve mejor que org.codehaus.jettison.json.JSONObject.quote("your string")

Ya utilizo las herramientas de velocidad en mi proyecto: mi construcción "manual JSON" estaba dentro de una plantilla de velocidad

Tjunkie
fuente
2

Para aquellos que vinieron aquí buscando una solución de línea de comandos, como yo, cURL's --data-urlencode funciona bien:

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

envía

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

, por ejemplo. Los datos JSON más grandes se pueden colocar en un archivo y usaría la sintaxis @ para especificar un archivo para sorber los datos que se van a escapar. Por ejemplo, si

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

usarías

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

Y ahora, este también es un tutorial sobre cómo consultar Freebase desde la línea de comandos :-)

vijucat
fuente
2

Utilice la clase EscapeUtils en la API de commons lang.

EscapeUtils.escapeJavaScript("Your JSON string");
theJ
fuente
1
Tenga en cuenta que las comillas simples, por ejemplo, se manejan de manera diferente cuando se escapa a javascript o json. En commons.lang 3.4 StringEscapeUtils ( commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/… ) tiene un método escapeJSON que es diferente del método escapeJavaScript en commons.lang 2: commons.apache. org / proper / commons-lang / javadocs / api-2.6 / org / ...
GlennV el
1

Considere la clase JsonWriter de Moshi . Tiene una API maravillosa y reduce la copia al mínimo, todo se puede transmitir a un archivo, 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 quieres la cuerda en la mano:

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

Si necesita escapar de JSON dentro de la cadena JSON, use org.json.JSONObject.quote ("su cadena json que necesita escapar") parece funcionar bien

webjockey
fuente
0

utilizando la sintaxis \ uXXXX puede resolver este problema, google UTF-16 con el nombre del signo, puede encontrar XXXX, por ejemplo: comillas dobles utf-16

David
fuente
0

Los métodos aquí que muestran la implementación real son todos defectuosos.
No tengo código Java, pero solo para el registro, puede convertir fácilmente este código C #:

Cortesía del proyecto mono @ 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();
}

Esto se puede compactar 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
fuente
¿Cómo es quote()defectuoso el método descrito en otras respuestas?
Sandy
0

Creo que la mejor respuesta en 2017 es usar las API javax.json. Use javax.json.JsonBuilderFactory para crear sus objetos json, luego escriba los objetos usando javax.json.JsonWriterFactory. Muy buena combinación de constructor / escritor.

herreros
fuente