beware JSON.stringify

TL;DR: Don't trust JSON.stringify to serialize objects returned by navigator.getCurrentPosition, or any other complex object with prototypical inheritance.  This will give you different results on different browsers. Use custom toJSON functions or use POJO/anonymous objects to serialize the values you need.

In modern javascript code, JSON.stringify() is really useful - it allows you to convert an object into a string which can in turn be converted back into an object again.  This is very often used when sending object data across the network to your server.  It is simple and it works well (almost all the time 🙂 )

Javascript objects are passed by reference so this can cause some issues if you are not careful.  One common case where using copies of objects might be useful is when initializing new objects with object properties of another object.  If you don't use a COPY of the object assigned to the property, any changes made to the object's properties will be made to BOTH references, as the properties/variables both point to the same instance of the object.  This is easily avoided by simply copying the object like so: = JSON.parse(JSON.stringify(object));

In certain environments, you may need to use a custom method for creating json representations of your object because framework code adds 'temporary' properties to your object for it's own purposes that you probably don't want copied to your new object.  Angular provides an angular.toJson() method for this purpose, which strips out properties with names starting with '$$'.  

All of this is fine and relatively well-known.  JSON.stringify is a reliable cross-browser tool that is used a lot - it 'just works'.  Until it doesn't 🙂

I spent the last week or more struggling with a really subtle bug in our html5 app that was only happening on IOS.  Certain properties of objects were being corrupted / removed.  However when debugging the objects all seemed well.  It turns out that JSON.stringify will only copy properties that are the objects 'own' properties  - ie the 'top level' properties of that object.  If you have a complex javascript object using prototypical inheritance, it will NOT copy properties that are from the prototype chain.  At least this is the case for SOME browsers (regardless of the specifications).  Basically once you're dealing with objects of a specific type (created with function constructors), you can't trust that JSON.stringify will do what you expect.  The JSON specifications do allow custom objects like this to provide their own .toJson() method, which JSON.stringify will use.  However not all library vendors (or browser vendors) bother to provide this feature.

The specific case I was dealing with was the location/position object provided by the javascript navigator.getCurrentPosition() method.  The callback provides a 'Geolocation' object with appropriate values in several properties (eg latitude, longitude).  This information is something we wanted to persist to our database, as well as persisting it in localStorage on the users's device (last known position).  javascript JSON.stringify(position) would return "{}" rather than the fully featured object we expected.  One of the reasons this was tricky to troubleshoot was that this position object would be copied about (using JSON.stringify) at a few different points in our application.  Most critically of course at the time we would save the data to our server.

In a mobile device environment, it's tricky to debug your code remotely.  We chose to provide a very simple view of the console.log calls on one of the screens in our app as a way for the user/developer to quickly check for interesting log messages while debugging unexpected behaviour.  Yes I'm aware of other remote debugging and error reporting solutions  (we've already implemented  But this option took 3 minutes to spin up and served it's purpose at the time.

However the simple code we were using to display the results was running objects through the JSON.stringify process in order to have a nice human readable view of the object you might be debugging.  In this case you'd see log statements showing you an empty object on one line, then the next line successfully printing out a property of that object. Very confusing 🙂

Luckily the workaround is pretty simple. You can provide/attach your own .toJson() method, or 'copy' the object's properties yourself into a pojo (plain old javascript object) as soon as you receive the object.  Once you're dealing with a pojo, JSON.stringify works wonderfully.  But be careful that ALL of the child properties of the object are copied over as native data types or pojos - in the case of the Geolocation object, it's coords property is another complex object that does not JSON.stringify correctly (on IOS at least).

This is the stackoverflow post that led me in the right direction:

a related post: