134

I'm thinking about embedding arbitrary JSON in the DOM like this:

<script type="application/json" id="stuff">
    {
        "unicorns": "awesome",
        "abc": [1, 2, 3]
    }
</script>

This is similar to the way one might store an arbitrary HTML template in the DOM for later use with a JavaScript template engine. In this case, we could later retrieve the JSON and parse it with:

var stuff = JSON.parse(document.getElementById('stuff').innerHTML);

This works, but is it the best way? Does this violate any best practice or standard?

Note: I'm not looking for alternatives to storing JSON in the DOM, I've already decided that's the best solution for the particular problem I'm having. I'm just looking for the best way to do it.

Paul Sweatte
  • 24,148
  • 7
  • 127
  • 265
Ben Lee
  • 52,489
  • 13
  • 125
  • 145
  • 2
    why won't you have it as a `var` in javascript? – Krizz Feb 16 '12 at 23:02
  • @Krizz, it needs to be part of a static document that it is later processed by a complex chain of encapsulated javascript. Storing it in the DOM is what I want to do. – Ben Lee Feb 16 '12 at 23:04
  • @Krizz I was posed with a problem similar. I wanted to put data in a site different for each user without doing a AJAX request. So I embedded some PHP in a container did something similar to what you have above to get the data in javascript. – Patrick Lorio Feb 16 '12 at 23:06
  • 2
    I think your original method is the best actually. It's 100% valid in HTML5, it is expressive, it doesn't create "fake" elements that you will just remove or hide with CSS; and it doesn't require any character encoding. What is the downside? – Jamie Treworgy Feb 16 '12 at 23:20
  • 26
    If you have a string with the value ` – silviot Apr 10 '14 at 16:18
  • @silviot if I were to blindly replace any `<` in the JSON string with `\u003C`, would that be enough? It seems too simple to really work... – badp Nov 10 '17 at 19:22

8 Answers8

90

I think your original method is the best. The HTML5 spec even addresses this use:

"When used to include data blocks (as opposed to scripts), the data must be embedded inline, the format of the data must be given using the type attribute, the src attribute must not be specified, and the contents of the script element must conform to the requirements defined for the format used."

Read here: http://dev.w3.org/html5/spec/Overview.html#the-script-element

You've done exactly that. What is not to love? No character encoding as needed with attribute data. You can format it if you want. It's expressive and the intended use is clear. It doesn't feel like a hack (e.g. as using CSS to hide your "carrier" element does). It's perfectly valid.

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
Jamie Treworgy
  • 23,934
  • 8
  • 76
  • 119
  • 3
    Thank you. The quote from the spec has convinced me. – Ben Lee Feb 16 '12 at 23:25
  • 18
    It's perfectly valid only if you check and sanitize the JSON object first: you can't just embed user originating data. See my comment on the question. – silviot Apr 10 '14 at 16:19
  • 1
    extra wondering : what is the good place to put it ? head or body, top or bottom ? – challet Mar 20 '17 at 15:32
  • 2
    Unfortunately, it [appears](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) that the CSP policy may/will stop all `script` tags. – Larry K Apr 18 '18 at 05:40
  • 3
    How do you guard effectively against embedding JSON that contains and, thus, allows HTML injection? Is there something solid/easy, or is it better to use data attributes? – jonasfj Aug 14 '19 at 09:33
21

As a general direction, I would try using HTML5 data attributes instead. There's nothing to stop you putting in valid JSON. e.g.:

<div id="mydiv" data-unicorns='{"unicorns":"awesome", "abc":[1,2,3]}' class="hidden"></div>

If you're using jQuery, then retrieving it is as easy as:

var stuff = JSON.parse($('#mydiv').attr('data-unicorns'));
Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
Horatio Alderaan
  • 3,264
  • 2
  • 24
  • 30
  • 1
    Make s sense. Though note that with single quotes for the key name, `JSON.parse` won't work (at least the native Google Chrome JSON.parse won't). The JSON spec requires double quotes. But that's easy enough to fix using entities like `...<unicorns>:...`. – Ben Lee Feb 16 '12 at 23:06
  • 5
    One question though: Is there any limit to the length of attributes in HTML 5? – Ben Lee Feb 16 '12 at 23:08
  • Yes, that would work. You could also switch it around so your HTML uses single quotes and the JSON data uses double. – Horatio Alderaan Feb 16 '12 at 23:08
  • 1
    Ok, found the answer to my question: http://stackoverflow.com/questions/1496096/is-there-a-limit-to-the-length-of-html-attributes -- this is plenty enough for my purposes. – Ben Lee Feb 16 '12 at 23:10
  • 5
    This wouldn't work for a single string, e.g. `"I am valid JSON"` and using double quotes for the tag, or single quotes with single quotes in the string, e.g. `data-unicorns='"My JSON's string"'` as single quotes aren't escaped with encoding as JSON. – scrowler Apr 28 '16 at 22:37
  • You could always use `escape(JSON.stringify({str: "'"}))` – andrsnn Dec 31 '16 at 01:07
16

This method of embedding json in a script tag has a potential security issue. Assuming the json data originated from user input, it is possible to craft a data member that will in effect break out of the script tag and allow direct injection into the dom. See here:

http://jsfiddle.net/YmhZv/1/

Here is the injection

<script type="application/json" id="stuff">
{
    "unicorns": "awesome",
    "abc": [1, 2, 3],
    "badentry": "blah </script><div id='baddiv'>I should not exist.</div><script type="application/json" id='stuff'> ",
}
</script>

There is just no way around escaping/encoding.

MadCoder
  • 161
  • 1
  • 2
  • 9
    This is true, but it's not really a security flaw of the method. If you are ever putting something that originated in user input into your pages, you have to be diligent about escaping it. This method is still sound as long as you take the normal precautionary measures regarding user input. – Ben Lee Mar 05 '14 at 00:06
  • JSON is not part of HTML, the HTML parser just keeps going. It's the same as when the JSON would be part of a text paragraph or div-element. HTML-escape the content in your program. In addition, you may also escape slashes. While JSON does not require this, it does tolerate unneeded slashes. Which can be used her for the purpose of making it safe to embed. PHP's json_encode does this by default. – Timo Tijhof Feb 26 '15 at 13:06
10

See Rule #3.1 in OWASP's XSS prevention cheat sheet.

Say you want to include this JSON in HTML:

{
    "html": "<script>alert(\"XSS!\");</script>"
}

Create a hidden <div> in HTML. Next, escape your JSON by encoding unsafe entities (e.g., &, <, >, ", ', and, /) and put it inside the element.

<div id="init_data" style="display:none">
        {&#34;html&#34;:&#34;&lt;script&gt;alert(\&#34;XSS!\&#34;);&lt;/script&gt;&#34;}
</div>

Now you can access it by reading the textContent of the element using JavaScript and parsing it:

var text = document.querySelector('#init_data').textContent;
var json = JSON.parse(text);
console.log(json); // {html: "<script>alert("XSS!");</script>"}
Matthew
  • 2,158
  • 7
  • 30
  • 52
  • 1
    I do believe this is the best and safest answer. Notice that a lot of common JSON characters get escaped, and certain characters get double escaped, such as the inner quotes in the object `{name: 'Dwayne "The Rock" Johnson'}`. But it's probably still best to use this approach since your framework/templating library likely already includes a safe way to do HTML encoding. An alternative would be to use base64 which is both HTML safe and safe to put inside a JS string. It's easy to encode/decode in JS using btoa()/atob() and it's probably easy for you to do server side. – sstur Jan 24 '19 at 05:21
  • An even safer method would be to use the semantically correct [`` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data) and include the JSON data in the `value` attribute. Then you only need to escape the quote marks with `&quot` if you use double quotes to enclose the data, or `'` if you use single quotes (which is probably better). – Rúnar Berg May 25 '19 at 22:26
8

HTML5 includes a <data> element for keeping machine readable data. As a—perhaps safer—alternative to <script type="application/json"> you could include your JSON data inside the value attribute of that element.

const jsonData = document.querySelector('.json-data');
const data = JSON.parse(jsonData.value);

console.log(data)
<data class="json-data" value='
  {
    "unicorns": "awesome",
    "abc": [1, 2, 3],
    "careful": "to escape &#39; quotes"
  }
'></data>

In this case you need to replace all single quotes with &#39; or with &quot; if you opt to enclose the value with double quotes. Otherwise your risk XSS attacks like other answers have suggested.

Rúnar Berg
  • 4,229
  • 1
  • 22
  • 38
6

I'd suggest to put JSON into an inline script with a function callback (kind of JSONP):

<script>
someCallback({
    "unicorns": "awesome",
    "abc": [1, 2, 3]
});
</script>

If the executing script is loaded after the document you can store this somewhere, possibly with an additional identifier argument: someCallback("stuff", { ... });

copy
  • 3,301
  • 2
  • 29
  • 36
  • @BenLee it should work very well, with the only disadvantage of having to define the callback function. The other suggested solution breaks on special HTML characters (for instance &) and quotes, if you have those in your JSON. – copy Feb 16 '12 at 23:20
  • This feels better because you dont need a dom query to find the data – Jaseem Dec 10 '12 at 19:42
  • @copy This solution still needs escaping (just a different kind), see MadCoder's answer. Just leaving it here for completeness. – pvgoran Dec 28 '17 at 06:32
  • This seems like it would be faster because the object is parsed directly as JavaScript rather than getting text from the DOM and going through `JSON.parse`. In my site, I do `const MY_DATA = ...`. – Indiana Kernick Dec 29 '20 at 22:46
1

My recommendation would be to keep JSON data in external .json files, and then retrieve those files via Ajax. You don't put CSS and JavaScript code onto the web-page (inline), so why would you do it with JSON?

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • 16
    You don't put CSS and Javascript inline in a web page because it's usually shared among other pages. If the data in question is generated by the server explicitly for this context, embedding it is far more efficient than initiating another request for something that cannot be cached. – Jamie Treworgy Feb 16 '12 at 23:19
  • It's because I'm making updates to a legacy system that was designed poorly, and rather than redesign the entire system, I need to just fix one part. Storing JSON in the DOM is the best way to fix this one part. Also, I agree with what @jamietre said. – Ben Lee Feb 16 '12 at 23:19
  • @jamietre Note that the OP stated that this JSON string is only needed *later*. The question is if it is needed always, or only in some cases. If it's only needed in some cases, then it does make sense to have it in an external file and only load it conditionally. – Šime Vidas Feb 16 '12 at 23:23
  • 3
    I agree that there are many "what ifs" that could tip the scale one way or the other. But generally speaking if you know when the page is rendered what you are going to need -- even if only possibly- it's often better to send it up right away. Like, if I had some information boxes that start collapesed, I'd usually like to include their contents inline so they expand instantly. The overhead a new request is a lot compared to the overhead of a bit of extra data on an existing one, and it creates a more responsive user experience. I am sure there is a break point. – Jamie Treworgy Feb 16 '12 at 23:28
  • Perhaps this should be deleted? It doesn't answer the question. OP wasn't asking a general of "how do I get data from the server to the client" and if they had, the question would have likely been removed for being too broad. – Abhi Beckert Feb 28 '22 at 04:26
0

I was going trough the code of random websites and I saw that they had JSON in JavaScript that's the magic word. If you want json in html do 2 things: html>JavaScript>JSON

<script>
//whatever code to put json in JavaScript(idk)
</script>
john cena
  • 9
  • 3