Skip to content
NorscodeNorscode

Reading and writing JSON

ExampleBy the Norscode project

Build a JSON response in a web service — and understand why escaping user data is not optional.

JSON is in the standard library, so you avoid a dependency for something as common as responding to an API call.

bruk std.web som web
bruk std.json som json

funksjon api_status(ctx: ordbok_tekst) -> ordbok_tekst {
    web.route("GET /api/status")
    la svar = "{" + json.streng("status") + ":" + json.streng("ok") + "}"
    returner web.response_builder(200, {"content-type": "application/json"}, svar)
}

funksjon start() -> heltall {
    skriv("API-et er klart")
    returner 0
}

What happens here

The content type application/json is not decoration. It tells the client how to interpret the response, and many client libraries refuse to parse a response marked as plain text. Set the wrong type, and you get debugging rounds that are about everything other than the actual error.

Notice that the key also goes through json.streng. It seems excessive when the key is a fixed text you wrote yourself, but the habit is worth having: the day the key comes from a variable, the code is already correct.

Escaping is not optional

This is the most important part of the recipe. If a value from the user is to go into JSON, it must be escaped.

// WRONG: the name may contain quotation marks and break out of the string
la farlig = "{" + json.streng("navn") + ":" + navn + "}"

// RIGHT: let the library escape the value
la trygg = "{" + json.streng("navn") + ":" + json.streng(navn) + "}"

Think through what happens in the first variant if someone registers with a name that contains a quotation mark. The string ends in the middle, and the rest of the name is interpreted as JSON structure. At best you get an invalid response. At worst, whoever wrote the name decides what the rest of the document looks like — and if the recipient trusts the fields, you have given away control.

What makes this error dangerous is that it does not show up in testing. All the names you try yourself work fine. The error only appears the day real user data hits the code.

Same rule, three places

The rule is the same for HTML, SQL and JSON: never build structured text by pasting user data together. Use the escape function for the format you are writing to.

FormatFunctionWhat it protects against
HTMLnf.html_escapeinput being run as script in the browser
SQLnf.sql_textinput changing what the query does
JSONjson.strenginput breaking out and changing the document structure

Three different functions because the three formats have different special characters. Using the wrong one is almost as bad as using none.

Reading JSON you have received

If you go the other way and receive JSON, one more rule applies: never trust that the structure is as you expect. A field can be missing, have the wrong type, or contain something entirely different from what the sender promised. Check before you use, and decide what should happen when something is wrong — before the error occurs, not after.

Related

Back to the overview