DEV Community

Cover image for JSON.stringify can create Multi-line, formatted and filtered strings from JSON
Christian Heilmann
Christian Heilmann

Posted on

JSON.stringify can create Multi-line, formatted and filtered strings from JSON

You can use JSON.stringify() to turn a JSON object into a string.

let obj = {"a": 1, "b": 3, "c": "boo!"};
console.log(JSON.stringify(obj))
Enter fullscreen mode Exit fullscreen mode

This results in a single line string:

{"a":1,"b":3,"c":"boo!"}
Enter fullscreen mode Exit fullscreen mode

However, you can also set two optional parameters, a filtering array or callback method and an indentation parameter. Setting the indentation to four, for example, creates a multi line string with 4 spaces indentation:

let obj = {"a": 1, "b": 3, "c": "boo!"};
console.log(JSON.stringify(obj, false, 4))
Enter fullscreen mode Exit fullscreen mode

Output:

{
    "a": 1,
    "b": 3,
    "c": "boo!"
}
Enter fullscreen mode Exit fullscreen mode

If instead of spaces you want tabs, you can also defined the indentation parameter as a string:

let obj = {"a": 1, "b": 3, "c": "boo!"};
console.log(JSON.stringify(obj, false, "\t"))
Enter fullscreen mode Exit fullscreen mode

Output:

{
    "a": 1,
    "b": 3,
    "c": "boo!"
}
Enter fullscreen mode Exit fullscreen mode

Or any other string:

let obj = {"a": 1, "b": 3, "c": "boo!"};
console.log(JSON.stringify(obj, false, "xxx"))
Enter fullscreen mode Exit fullscreen mode

Output:

{
xxx"a": 1,
xxx"b": 3,
xxx"c": "boo!"
}
Enter fullscreen mode Exit fullscreen mode

You can define an array of keys you want to show to filter the outcome:

let obj = {"a": 1, "b": 3, "c": "boo!"};
console.log(JSON.stringify(obj, ['a','c'], 4))
Enter fullscreen mode Exit fullscreen mode

Output:

{
    "a": 1,
    "c": "boo!"
}
Enter fullscreen mode Exit fullscreen mode

And you can write a filtering callback function that gets applied to the JSON object. For example, to only allow for numbers to show:

const onlyNumbers = (key,value) => { 
  return (typeof value === 'string')  ? undefined : value 
}
let obj = {"a": 1, "b": 3, "c": "boo!"};
console.log(JSON.stringify(obj, onlyNumbers, 4))
Enter fullscreen mode Exit fullscreen mode

Output:

{
    "a": 1,
    "b": 3
}
Enter fullscreen mode Exit fullscreen mode

You can see more examples on MDN.

Whilst I like these options, it always feels weird to me when a method allows for different values to determine what to do. Having the replacer either be an array or a callback and the spaces option be a number or a string feels confusing. What do you think?

Top comments (2)

Collapse
 
teetotum profile image
Martin • Edited

Nice, I learned something new today.
In your closing question you ask about our thoughts regarding the design of the API; so here are mine:

  • I don't think it particularly elegant that it mixes separate use-cases into one function. But it isn't exactly horrible either. I can live with it.
  • The first and probably more important use-case is: serializing an object, to allow transferring it beyond the boundary of the js runtime memory, using the JSON format. For this case linebreaks and indentation shouldn't exist. It should be as small as possible. And to go further: I wouldn't have named it JSON.stringify() but instead would have named it JSON.serialize()
  • The second use-case is: taking a JSON representation of data and formatting it nicely for better readability when displayed. And it shouldn't matter whether we received some JSON via the wire or whether we just created it from an object. To me the most natural function design for this case would be a JSON.format(input: string, options?: FormattingOptions) that only operates on a string, not on an object. If I wanted to serialize and format an object I would need to call JSON.format(JSON.serialize(foo)) and I wouldn't roll both cases into one
  • next thought: Performance consideration. Maybe it is reasonable for cases of very large objects to only travers the data once, and doing serialization and formatting at the same time. This would be impossible if we only had two distinct functions serialize and format. So maybe I must revise my earlier thought and allow for the format function to also accept objects, so it can serialize and format data in one go.
  • which leads me to another thought: I could now serialize data with format and just use the minimal formatting which produces no linebreaks and no indentation. The output would be exactly equal to the output from serialize. So I could get rid of serialize to keep my API nice and lean. And now I've come full circle, and I should probably rename my format function to better convey that it can do both: formatting and serialization. What to name it? Yes, maybe we name it .stringify()
  • final thought: Well, this was fun. I had those insights while typing my comment. And it somewhat made me appreciate the work of spec authors a bit more. Coming up with a good API is an art form as much as it is engineering.
Collapse
 
krofdrakula profile image
Klemen Slavič

I agree; the API is so fuzzy with all the different ways the parameters get interpreted that it's hard to read a call to stringify and know what it's doing.

Having said that, I find the replacer to be very useful: