Skip to content

Instantly share code, notes, and snippets.

@jed

jed/replacer.coffee

Created Jul 28, 2011
Embed
What would you like to do?
JSON.stringify replacer that sorts object keys
replacer = (key, val) ->
if val instanceof Object
keys = Object.keys(val).sort().map (key) ->
"\"#{key}\":#{JSON.stringify val[key], replacer}"
"{#{keys}}"
else val
@jed

This comment has been minimized.

Copy link
Owner Author

@jed jed commented Jul 28, 2011

i'm looking for a JSON.stringify replacer that turns {c:1,b:2,a:3} into {"a":3,"b":2,"c":1}... this one ends up overquoting nested keys. ideas?

@polotek

This comment has been minimized.

Copy link

@polotek polotek commented Jul 28, 2011

Interesting. Why would it overquote? Do you have test input?

@jed

This comment has been minimized.

Copy link
Owner Author

@jed jed commented Jul 28, 2011

i'm pretty sure the problem is that JSON.stringify runs itself again on any string you return. so you can't return a string that's not escaped.

The value returned will be stringified.
from http://www.json.org/js.html

@polotek

This comment has been minimized.

Copy link

@polotek polotek commented Jul 28, 2011

I'm looking at the reference implementation. https://github.com/douglascrockford/JSON-js/blob/master/json2.js. I don't see a way to do what you want. You're right that you can't return a string from the replacer, everything always goes through serialization at the end. And it uses a for..in loop over the keys, so at no point can you insert your own custom ordering.

I thought maybe you could use the other form of "replacer". It takes an array specifying the properties to be included in serialization. Unfortunately, this seems to be a global array and you can't be customized for each iteration. I don't know what your use case is. But you could do something reeeally hacky, like get all keys from the object and sort them by alpha first, and use that as the replacer. That might work, provided that the serializer skips undefined values. I believe it does.

@jed

This comment has been minimized.

Copy link
Owner Author

@jed jed commented Jul 28, 2011

@polotek,

wow, thanks for the thoughtful answer! yeah, i think this is a no-go. best bet would probably return a new object with keys defined in order and hope the engine respects it (which of course it's not required to).

oh, and next time i'll write in in JS for ya, heh.

@polotek

This comment has been minimized.

Copy link

@polotek polotek commented Jul 28, 2011

Thanks for the midday diversion. Let me know if you come up with something workable.

@ncr

This comment has been minimized.

Copy link

@ncr ncr commented Mar 12, 2012

The following works for me, do you see any potential problems with it? Tested it with deeply nested objects, so far never broke.

replacer = (key, value) ->
  return value unless value.constructor is Object
  Object.keys(value).sort().reduce (sorted, key) ->
    sorted[key] = value[key]
    sorted
  , {}
@jed

This comment has been minimized.

Copy link
Owner Author

@jed jed commented Mar 13, 2012

@ncr, this still relies on order of insertion. try it with {1a: 1, 22: 1} on v8 and you'll get {"22":1,"1a":1}.

@ncr

This comment has been minimized.

Copy link

@ncr ncr commented Mar 13, 2012

@jed the quirk with keys that are parseable as 32bit integers is actually not a problem in my case - I use this code in v8 only, so the key here is not the actual "sorting" but the stability of returned results.

I use this function to ensure that a stringified JSON looks always identical for a given equivalent data so I can sign it.

@jed

This comment has been minimized.

Copy link
Owner Author

@jed jed commented Mar 13, 2012

indeed, assuming v8 doesn't change their iteration implementation. i like the use of reduce too (though not a huge fan of coffee syntax for arguments following functions).

@lionello

This comment has been minimized.

Copy link

@lionello lionello commented Jul 22, 2018

@ncr Thanks, that worked for me, but had to add a null check. In regular JS:

function replacer (key, value) {
  if (value == null || value.constructor != Object) {
    return value
  }
  return Object.keys(value).sort().reduce((s,k) => {s[k] = value[k]; return s}, {})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment