Najlepszy sposób na przechowywanie JSON w atrybucie HTML?

112

Muszę umieścić obiekt JSON w atrybucie elementu HTML.

  1. Kod HTML nie musi sprawdzać poprawności.

    Odpowiedział Quentin: Przechowuj JSON w data-*atrybucie , który jest prawidłowym HTML5.

  2. Obiekt JSON może mieć dowolny rozmiar - tj. Ogromny

    Odpowiedział Maiku Mori: Limit dla atrybutu HTML to potencjalnie 65536 znaków .

  3. Co się stanie, jeśli JSON zawiera znaki specjalne? na przykład {foo: '<"bar/>'}

    Odpowiedział Quentin: Zakoduj ciąg JSON przed umieszczeniem go w atrybucie, zgodnie ze zwykłymi konwencjami. W przypadku PHP użyj funkcji . htmlentities()


EDYCJA - Przykładowe rozwiązanie wykorzystujące PHP i jQuery

Zapisywanie JSON w atrybucie 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>

Pobieranie JSON za pomocą 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
źródło
Prawdopodobnie powinieneś wyjaśnić dlaczego i poprosić o inne rozwiązanie, ponieważ jestem pewien, że nie jest to najlepsze. Możesz prawdopodobnie użyć atrybutów typu data-coś, ale nie jestem pewien, czy mogą pomieścić „ogromną” ilość tekstu. Jeśli chodzi o znaki specjalne, możesz po prostu zakodować (escape () i unescape ()) tekst.
Maiku Mori
Tak, limit to 65536 znaków ( stackoverflow.com/questions/2752457/… )
Maiku Mori,
1
Przy okazji, jeśli twój atrybut jest nazwany, data-jsonktórego powinieneś użyć $(this).data('json'), jQuery ma cię w tej części.
Ciantic
Uwaga, nazywanie sufiksu danych na json nie jest wymagane. Jeśli umieścisz poprawny json w dowolnym atrybucie data-custom_attribute, będzie działał dobrze z jQuery.
Garet Claborn
popraw kolejność nawiasów klamrowych)}; =>});
MotsManish

Odpowiedzi:

41

Kod HTML nie musi sprawdzać poprawności.

Dlaczego nie? Walidacja to naprawdę łatwa kontrola jakości, która wyłapuje wiele błędów. Użyj data-*atrybutu HTML 5 .

Obiekt JSON może mieć dowolny rozmiar (tj. Ogromny).

Nie widziałem żadnej dokumentacji dotyczącej ograniczeń przeglądarek dotyczących rozmiarów atrybutów.

Jeśli napotkasz je, zapisz dane w pliku <script>. Zdefiniuj obiekt i przypisz element ids do nazw właściwości w tym obiekcie.

Co się stanie, jeśli JSON zawiera znaki specjalne? (np. {test: '<"myString />'})

Po prostu postępuj zgodnie ze zwykłymi regułami włączania niezaufanych danych do wartości atrybutów. Użyj &amp;i &quot;(jeśli zawijasz wartość atrybutu w podwójne cudzysłowy) lub &#x27;(jeśli zawijasz wartość atrybutu w pojedyncze cudzysłowy).

Należy jednak pamiętać, że nie jest to format JSON (który wymaga, aby nazwy właściwości były ciągami, a ciągi były rozdzielane tylko podwójnymi cudzysłowami).

Quentin
źródło
5
Więc mówisz, że powinienem to zrobić, htmlentities($json)zanim wstawię go do atrybutu HTML? A potem jak to zdekodować, gdy chcę to przeczytać w jQuery? A potem, jak mam to zapisać z powrotem w jQuery w taki sam sposób, jak w PHP?
BadHorsie,
6
Więc mówisz, że powinienem zrobić html_encode ($ json), zanim wstawię go do atrybutu HTML? - jeśli używasz PHP, to zadziała. A potem jak to zdekodować, gdy chcę to przeczytać w jQuery? - Odszyfrować z atrybutu? Przeglądarka zrobi to, gdy przetwarza HTML do DOM. A potem, jak mam to zapisać z powrotem w jQuery w taki sam sposób, jak w PHP? - Ustawiasz atrybuty węzłów DOM, a nie generujesz surowy HTML, przeglądarka się tym zajmie.
Quentin,
1
W tej chwili mam problem polegający na tym, że moja przeglądarka nie dekoduje jej obecnie w Google Chrome, a kiedy przechodzę do analizowania JSON, wszystkie encje HTML są tam i zawodzą.
Bradley Weston
Jeśli umieścisz go w tagu script, musisz uciec przed nim w inny sposób ze względu na specjalną obsługę tagów script. np. wartość zawierająca </script> zakończyłaby tag skryptu.
Dobes Vandermeer
16

W zależności od tego, gdzie go umieścisz,

  • W sposób <div>jak pytasz, trzeba upewnić się, że JSON nie zawiera specjalne HTML, który mógłby rozpocząć komentarz, tag HTML, wbudowany doctype, itd. Musisz uciec przynajmniej <, i &w taki sposób, że oryginalna postać nie pojawiają się w sekwencji ucieczki.
  • W <script>elementach musisz upewnić się, że JSON nie zawiera tagu końcowego </script>ani granicy ucieczki tekstu: <!--lub -->.
  • W przypadku obsługi zdarzeń musisz upewnić się, że JSON zachowuje swoje znaczenie, nawet jeśli zawiera elementy wyglądające jak encje HTML i nie narusza granic atrybutów ( "lub ').

W pierwszych dwóch przypadkach (i dla starych parserów JSON) należy zakodować U + 2028 i U + 2029, ponieważ są to znaki nowej linii w JavaScript, mimo że są dozwolone w ciągach niezakodowanych w JSON.

Aby uzyskać poprawność, musisz uciekać \i cytować znaki JSON i nigdy nie jest złym pomysłem zawsze kodować NUL.

Jeśli kod HTML może być obsługiwany bez kodowania treści, należy kodować, +aby zapobiec atakom UTF-7 .

W każdym razie poniższa tabela zmiany znaczenia będzie działać:

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

Zatem wartość ciągu JSON dla tekstu Hello, <World>!z nową linią na końcu będzie "Hello, \u003cWorld\u003e!\r\n".

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

Innym sposobem, w jaki możesz to zrobić, jest umieszczenie danych json w <script>tagu, ale nie za pomocą type="text/javascript", ale z type="text/bootstrap"lub type="text/json"typem, aby uniknąć wykonywania javascript.

Następnie w jakimś miejscu swojego programu możesz poprosić o to w ten sposób:

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;
  } 
}

Po stronie serwera możesz zrobić coś takiego (ten przykład z php i twig ):

<script id="my_model" type="text/json">
  {{ my_model|json_encode()|raw }}
</script>
Sergey Kamardin
źródło
1
W przypadku formatu JSON użyj typu skryptu „application / json”. Fajnie jest też mieć najwyższy poziom jako obiekt na dłuższą metę.
OIS
1
To nie odpowiada bezpośrednio na pytanie operatora, ale nadal było dla mnie bardzo pomocne. Dane, które przechowuję, dotyczą całej strony, a nie żadnego konkretnego elementu, więc atrybut elementu tak naprawdę nie działa (chyba że umieściłem go tak, jak w elemencie body, który wydaje mi się trochę kiepski, zwłaszcza że mój dane mogą być duże). Przechowywanie go w a <script type="application/json" id="MyData"></script>działa idealnie. Następnie, używając ActiveWAFL / DblEj, mogę szukać go z: document.GetData("#MyData").
Evan de la Cruz
2
Ta odpowiedź zawiera fałszywe / niebezpieczne informacje! Zmiana typu skryptu nie modyfikuje wykrywania tagów </script>. Po prostu spróbuj:<script type="application/json" id="MyData"> "abc</script><script>alert()</script>" </script>
hyperknot
12

Inną opcją jest kodowanie ciągu JSON przy użyciu base64 i jeśli chcesz go użyć w swoim javascript, zdekoduj go za pomocą atob()funkcji.

var data = JSON.parse(atob(base64EncodedJSON));
Pavel Petrov
źródło
Dzięki za atob. Nigdy nie byłem tego świadomy!
Ifedi Okonkwo
3
Uwaga - nie działa, jeśli JSON zawiera znaki inne niż łacińskie.
Realexer,
5

Możesz użyć 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());

Wynik

Imię: Jayson Nazwisko: Monterroso

Sprawdź to: http://learn.knockoutjs.com/

jayM
źródło
2

Nie ma tu nic nadzwyczajnego. W PHP przeprowadź ciąg JSON, htmlspecialcharsaby upewnić się, że żadne znaki specjalne nie mogą być interpretowane jako HTML. Z Javascript nie trzeba uciekać; po prostu ustaw atrybut i gotowe.

Matchu
źródło
2

Poniższy kod zadziała w przypadku prostych obiektów JSON.

Kodować:

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

Rozszyfrować:

var jsonString = unescape(attributeString);
var jsonObject = JSON.parse(jsonString);
Crashalot
źródło
1

To, co możesz zrobić, to użyć cdata wokół swojego elementu / elementów w ten sposób

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

gdzie mydata to nieprzetworzony ciąg json. Mam nadzieję, że to pomoże tobie i innym.

Faiyet
źródło
Jak działa (może) to rozwiązanie? A jeśli chcę przechowywać coś takiego ">w moich danych?
Martin Pecka
1
Co jeśli ciąg zawiera „]]>”
Dobes Vandermeer
1

Inną myślą, której można by użyć, jest zapisanie danych JSON jako ciągu base64 w atrybucie, a następnie użycie window.atoblub w window.btoacelu przywrócenia ich do użytecznych danych JSON.

<?php
$json = array("data"=>"Some json data thing");
echo "<div data-json=\"".base64_encode(json_encode($json))."\"></div>";
?>
Matthew D Auld
źródło