¿La mejor manera de almacenar JSON en un atributo HTML?

112

Necesito poner un objeto JSON en un atributo en un elemento HTML.

  1. El HTML no tiene que validar.

    Respondido por Quentin: Almacene el JSON en un data-*atributo , que es HTML5 válido.

  2. El objeto JSON podría tener cualquier tamaño, es decir, enorme

    Respuesta de Maiku Mori: El límite para un atributo HTML es potencialmente de 65536 caracteres .

  3. ¿Qué pasa si el JSON contiene caracteres especiales? p.ej {foo: '<"bar/>'}

    Respondido por Quentin: Codifique la cadena JSON antes de ponerla en el atributo, según las convenciones habituales. Para PHP, use la función . htmlentities()


EDITAR - Solución de ejemplo usando PHP y jQuery

Escribiendo el JSON en el atributo HTML:

<?php
    $data = array(
        '1' => 'test',
        'foo' => '<"bar/>'
    );
    $json = json_encode($data);
?>

<a href="#" data-json="<?php echo htmlentities($json, ENT_QUOTES, 'UTF-8'); ?>">CLICK ME</a>

Recuperando el JSON usando jQuery:

$('a').click(function() {

    // Read the contents of the attribute (returns a string)
    var data = $(this).data('json');

    // Parse the string back into a proper JSON object
    var json = $.parseJSON($(this).data('json'));

    // Object now available
    console.log(json.foo);

});
BadHorsie
fuente
Probablemente debería explicar por qué y pedir una solución diferente, ya que estoy bastante seguro de que esta no es la mejor. Probablemente puede usar atributos de datos, pero no estoy seguro de que puedan contener una cantidad "enorme" de texto. En cuanto a los caracteres especiales, simplemente puede codificar (escape () y unescape ()) el texto.
Maiku Mori
Sí, el límite es 65536 caracteres ( stackoverflow.com/questions/2752457/… )
Maiku Mori
1
Por cierto, si su atributo tiene un nombre data-jsonque debe usar $(this).data('json'), jQuery lo tiene cubierto en esa parte.
Ciantic
Solo una nota, no es necesario nombrar el sufijo de datos a json. Si coloca un json válido en cualquier atributo_personalizado de datos, funcionará bien con jQuery.
Garet Claborn
corrija la secuencia de llaves)}; =>});
MotsManish

Respuestas:

41

El HTML no tiene que validar.

Por qué no? La validación es un control de calidad realmente fácil que detecta muchos errores. Utilice un data-*atributo HTML 5 .

El objeto JSON puede tener cualquier tamaño (es decir, enorme).

No he visto ninguna documentación sobre los límites del navegador para los tamaños de los atributos.

Si se encuentra con ellos, almacene los datos en un archivo <script>. Defina un objeto y asigne elementos ida los nombres de propiedad en ese objeto.

¿Qué pasa si el JSON contiene caracteres especiales? (por ejemplo, {prueba: '<"myString />'})

Simplemente siga las reglas normales para incluir datos que no sean de confianza en los valores de los atributos. Use &amp;y &quot;(si está envolviendo el valor del atributo entre comillas dobles) o &#x27;(si está envolviendo el valor del atributo entre comillas simples).

Sin embargo, tenga en cuenta que eso no es JSON (que requiere que los nombres de propiedad sean cadenas y que las cadenas estén delimitadas solo con comillas dobles).

Quentin
fuente
5
¿Entonces estás diciendo que debería hacerlo htmlentities($json)antes de ponerlo en el atributo HTML? ¿Y luego cómo decodifico eso cuando quiero leerlo en jQuery? Y luego, ¿cómo lo vuelvo a escribir usando jQuery de la misma manera que estaba en PHP?
BadHorsie
6
¿Estás diciendo que debería hacer html_encode ($ json) antes de ponerlo en el atributo HTML? - Si está usando PHP, entonces funcionaría. ¿Y luego cómo decodifico eso cuando quiero leerlo en jQuery? - ¿Decodificar del atributo? El navegador hará eso cuando analiza el HTML en un DOM. Y luego, ¿cómo lo vuelvo a escribir usando jQuery de la misma manera que estaba en PHP? - Estás configurando atributos de nodos DOM, no generando HTML sin formato, el navegador se encargará de ello.
Quentin
1
En este momento tengo un problema en el que mi navegador no lo está decodificando actualmente en Google Chrome, y cuando voy a analizar JSON, todas las entidades HTML están ahí y fallan.
Bradley Weston
Si lo coloca en una etiqueta de secuencia de comandos, debe escapar de forma diferente debido al manejo especial de las etiquetas de secuencia de comandos. por ejemplo, un valor con </script> terminaría la etiqueta del script.
Dobes Vandermeer
16

Dependiendo de donde lo pongas

  • Como <div>lo solicitó, debe asegurarse de que el JSON no contenga especiales HTML que puedan iniciar una etiqueta, comentario HTML, tipo de documento incrustado, etc. Debe escapar al menos <, y &de tal manera que el carácter original no lo haga aparecen en la secuencia de escape.
  • En los <script>elementos, debe asegurarse de que JSON no contenga una etiqueta final </script>o un límite de texto de escape: <!--o -->.
  • En los controladores de eventos, debe asegurarse de que JSON conserve su significado incluso si tiene cosas que parecen entidades HTML y no rompe los límites de los atributos ( "o ').

Para los dos primeros casos (y para los analizadores JSON antiguos), debe codificar U + 2028 y U + 2029, ya que son caracteres de nueva línea en JavaScript, aunque están permitidos en cadenas no codificadas en JSON.

Para que sea correcto, debe usar caracteres de escape \y comillas JSON y nunca es una mala idea codificar siempre NUL.

Si es posible que el HTML se sirva sin codificación de contenido, debe codificarlo +para evitar ataques UTF-7 .

En cualquier caso, la siguiente tabla de escape funcionará:

  • NUL -> \u0000
  • CR -> \no\u000a
  • LF -> \ro\u000d
  • " -> \u0022
  • & -> \u0026
  • ' -> \u0027
  • + -> \u002b
  • /-> \/o\u002f
  • < -> \u003c
  • > -> \u003e
  • \-> \\o\u005c
  • U + 2028 ->\u2028
  • U + 2029 -> \u2029

Entonces, el valor de cadena JSON para el texto Hello, <World>!con una nueva línea al final sería "Hello, \u003cWorld\u003e!\r\n".

Mike Samuel
fuente
1
return (input.replace(/([\s"'& + \ / \\ <> \ u2028 \ u2029 \ u0000]) / g, (coincidencia, p1) => {return \\u${p1.codePointAt(0).toString(16).padStart(4, 0)}; })); `
Denis Giffeler
14

Otra forma de hacerlo es poner los datos json dentro de la <script>etiqueta, pero no con type="text/javascript", sino con type="text/bootstrap"o type="text/json"tipo, para evitar la ejecución de JavaScript.

Luego, en algún lugar de tu programa, puedes solicitarlo de esta manera:

function getData(key) {
  try {
    return JSON.parse($('script[type="text/json"]#' + key).text());
  } catch (err) { // if we have not valid json or dont have it
    return null;
  } 
}

En el lado del servidor, puede hacer algo como esto (este ejemplo con php y twig ):

<script id="my_model" type="text/json">
  {{ my_model|json_encode()|raw }}
</script>
Sergey Kamardin
fuente
1
Para JSON, utilice el tipo de secuencia de comandos "application / json". También es bueno tener el nivel superior como un objeto a largo plazo.
OIS
1
Esto no responde directamente a la pregunta de la operación, pero aún así fue muy útil para mí. Los datos que estoy almacenando se aplican a la página como un todo y no a ningún elemento específico, por lo que un atributo de elemento no funciona realmente (a menos que lo ponga como en el elemento del cuerpo, que me parece un poco escaso, especialmente porque mi los datos pueden ser grandes). Almacenarlo en un <script type="application/json" id="MyData"></script>funciona perfectamente. Luego, utilizando ActiveWAFL / DblEj, puedo mirar hacia arriba con: document.GetData("#MyData").
Evan de la Cruz
2
¡Esta respuesta contiene información falsa / peligrosa! Cambiar el tipo de secuencia de comandos no modifica la detección de etiquetas </script>. <script type="application/json" id="MyData"> "abc</script><script>alert()</script>" </script>
Prueba
12

Otra opción es codificar en base64 la cadena JSON y, si necesita usarla en su javascript, decodificarla con la atob()función.

var data = JSON.parse(atob(base64EncodedJSON));
Pavel Petrov
fuente
Gracias por atob. ¡Nunca me di cuenta de eso!
Ifedi Okonkwo
3
Cuidado: no funciona si JSON contiene caracteres no latinos.
Realexer
5

Puedes usar knockoutjs,

<p>First name: <strong data-bind="text: firstName" >todo</strong></p>
<p>Last name: <strong data-bind="text: lastName">todo</strong></p>

knockout.js

// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
    this.firstName = "Jayson";
    this.lastName = "Monterroso";
}

// Activates knockout.js
ko.applyBindings(new AppViewModel());

Salida

Nombre: Jayson Apellido: Monterroso

Compruebe esto: http://learn.knockoutjs.com/

JayM
fuente
2

Nada especial aquí. Desde PHP, ejecute la cadena JSON htmlspecialcharspara asegurarse de que ningún carácter especial pueda interpretarse como HTML. Desde Javascript, no es necesario escapar; solo establece el atributo y listo.

Matchu
fuente
2

Para objetos JSON simples, el siguiente código funcionaría.

Codificar:

var jsonObject = { numCells: 5, cellWidth: 1242 };
var attributeString = escape(JSON.stringify(jsonObject));

Descodificar:

var jsonString = unescape(attributeString);
var jsonObject = JSON.parse(jsonString);
Crashalot
fuente
1

Lo que puede hacer es usar cdata alrededor de su elemento / s de esta manera

<![CDATA[  <div class='log' mydata='${aL.logData}'>${aL.logMessage}</div>     ]]>  

donde mydata es una cadena json sin procesar. Espero que esto te ayude a ti y a los demás.

Faiyet
fuente
¿Cómo funciona (puede) esta solución? ¿Qué pasa si quiero almacenar algo como ">en mydata?
Martin Pecka
1
¿Qué pasa si la cadena contiene "]]>"
Dobes Vandermeer
1

Otro pensamiento que podría usarse es almacenar los datos JSON como una cadena base64 en el atributo y luego usar window.atobo window.btoarestaurarlos a datos JSON utilizables.

<?php
$json = array("data"=>"Some json data thing");
echo "<div data-json=\"".base64_encode(json_encode($json))."\"></div>";
?>
Matthew D Auld
fuente