FF 13, IE 9: JSON stringify / geolocation object
Asked Answered
L

5

21

I'm trying to get Firefox 13 to turn a geolocation position object into a JSON string, but it's returning an empty string rather than the correct string representation of my JSON object. This is working fine in the latest versions of Chrome and Safari, as well as the Android browser. Here's my code:

if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition( 
        function (position) {  
            //Success handler
            console.log(position); //This outputs the position object to the console
            var gps = JSON.stringify(position); 
            console.log(gps); //This outputs an empty string!
        }, 
        function (error)
        {   
            //Handle error
        },
        { maximumAge: 3000, timeout: 60000, enableHighAccuracy: true }
        );
}
else {
    //Handle error
}

In Chrome, this outputs a geolocation object, and this string:

"{"coords":{"latitude":XYZ,"heading":null,"accuracy":40,"altitudeAccuracy":null,"altitude":null,"longitude":XYZ,"speed":null},"timestamp":1339712284200}"

However, in Firefox 13 the output is just an empty string, even though the geolocation object that's printed to the console is to all intents and purposes the same as the object displayed by Chrome. Any ideas on what's going wrong here? This seems to be a related issue, but I don't see a solution there either. IE9 displays the same behaviour, by the way.

Liliuokalani answered 14/6, 2012 at 22:26 Comment(0)
E
13

What's going on is that JSON.stringify only looks at the object's own properties by default.

And per DOM specs all DOM properties actually live on the object's prototype.

IE and Firefox implement the spec correctly by putting the properties on the prototype. Chrome and Safari do not: they put the properties directly on the object. That makes this case work, but breaks other things (e.g. the ability to hook the property getters and setters)....

There's talk of adding toJSON methods to some DOM objects to give them more reasonable behavior for JSON.stringify.

Excitement answered 15/6, 2012 at 5:17 Comment(4)
geolocation has nothing to do with DOM.Pebble
Thanks! I did figure out a workaround is simply to assign the properties to a new variable and stringify that, but it was unclear to me why that worked while my earlier code didn't, which I didn't like. Now I understand.Liliuokalani
@Pumbaa80 More precisely, the WebIDL specification defines the behavior here. But feel free to nitpick as desired!Excitement
Chrome has since implemented this "correctly" and now its Positions and Coordinates don't stringify anymore either.Laveta
I
31

I created a clone function to clone the Geolocation position (or any other) object into an object that will be stringified as expected:

function cloneAsObject(obj) {
    if (obj === null || !(obj instanceof Object)) {
        return obj;
    }
    var temp = (obj instanceof Array) ? [] : {};
    // ReSharper disable once MissingHasOwnPropertyInForeach
    for (var key in obj) {
        temp[key] = cloneAsObject(obj[key]);
    }
    return temp;
}

Note: May not support types not used in Geoposition type (eg Date)

You would then use it as follows in your code:

var gps = JSON.stringify(cloneAsObject(position)); 

Hope this helps someone :)

Instantaneous answered 9/6, 2016 at 12:56 Comment(1)
I wish this was the accepted answer. I passed this up initially, then 20 minutes later found a blog post that pointed back here. Could have saved me some time.Pelota
E
13

What's going on is that JSON.stringify only looks at the object's own properties by default.

And per DOM specs all DOM properties actually live on the object's prototype.

IE and Firefox implement the spec correctly by putting the properties on the prototype. Chrome and Safari do not: they put the properties directly on the object. That makes this case work, but breaks other things (e.g. the ability to hook the property getters and setters)....

There's talk of adding toJSON methods to some DOM objects to give them more reasonable behavior for JSON.stringify.

Excitement answered 15/6, 2012 at 5:17 Comment(4)
geolocation has nothing to do with DOM.Pebble
Thanks! I did figure out a workaround is simply to assign the properties to a new variable and stringify that, but it was unclear to me why that worked while my earlier code didn't, which I didn't like. Now I understand.Liliuokalani
@Pumbaa80 More precisely, the WebIDL specification defines the behavior here. But feel free to nitpick as desired!Excitement
Chrome has since implemented this "correctly" and now its Positions and Coordinates don't stringify anymore either.Laveta
S
3

Old question but I came here because stringify did not work for me either.

After googling for all kind of clone-functions I took a different approach.

export async function getLocation(): Promise<Position> {
    return new Promise((resolve, reject) => {
        if (navigator.geolocation) {
            navigator.geolocation.getCurrentPosition((position: Position) =>
                resolve({
                    coords: {
                        accuracy: position.coords.accuracy,
                        altitude: position.coords.altitude,
                        altitudeAccuracy: position.coords.altitudeAccuracy,
                        heading: position.coords.heading,
                        latitude: position.coords.latitude,
                        longitude: position.coords.longitude,
                        speed: position.coords.speed,
                    },
                    timestamp: position.timestamp,
                }),
            );
        } else {
            reject(new Error('Browser does not support geolocation!'));
        }
    });
}

getLocation returns a "stringifyable" Position object!

Sukiyaki answered 13/12, 2019 at 11:20 Comment(0)
L
1

Simplest I could find was using Lodash

lodash.defaultsDeep({coords: {}}, p)

Install with npm i lodash.defaultsdeep, then

import defaultsDeep from 'lodash.defaultsdeep'; 
//const defaultsDeep = require('lodash.defaultsdeep'); //CommonJS/Node


JSON.stringify(defaultsDeep({coords: {}}, p))

//Outputs:
// "{
//   "coords": {
//     "latitude": 55.6300000,
//     "longitude": -6.1520000,
//     "altitude": null,
//     "accuracy": 1794,
//     "altitudeAccuracy": null,
//     "heading": null,
//     "speed": null
//   },
//   "timestamp": 1601361646336
// }"
Linnlinnaeus answered 29/9, 2020 at 7:6 Comment(0)
G
0

Just an update on Mike's answer:

export function getUserPosition(): Promise<GeolocationPosition> {
  return new Promise((resolve, reject) => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        (position: GeolocationPosition) =>
          resolve({
            coords: {
              accuracy: position.coords.accuracy,
              altitude: position.coords.altitude,
              altitudeAccuracy: position.coords.altitudeAccuracy,
              heading: position.coords.heading,
              latitude: position.coords.latitude,
              longitude: position.coords.longitude,
              speed: position.coords.speed,
            },
            timestamp: position.timestamp,
          }),
        (err) => {
          reject(err);
        },
        {
          timeout: GET_CURRENT_POSITION_TIMEOUT,
          maximumAge: GET_CURRENT_POSITION_MAX_AGE,
          /* 
          There was an issue with location in Chrome 89 if this wasn't set to `false`,
          so the best solution is set it to false in dev & test and undefine it in production
          */
          enableHighAccuracy:
            process.env.NODE_ENV !== 'production' ? false : undefined,
        }
      );
    } else {
      reject(new Error('Browser does not support geolocation!'));
    }
  });
}
Granulose answered 9/6, 2021 at 10:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.