Search code examples
javascriptjsonstringify

JSON.stringify() losing nested properties I know the cause but don't know the answer


I have 2 objects:

const subscription = {
 endpoint: "dfksjfklsjkld",
 keys: {
    pkey: "dfsfsdfsf",
    auth: "dfsdfsdfsd"
 }
};

const extra = {
  email: "dfsdfs",
  ip:"231342.342.342.34"
};

I would like to put the extra object inside subscription, so it looks like:

subsciption = {
 endpoint: ......
 keys: {...},
 extra: {
    email:....,
    ip: .....
 }
}

then I need to send it as body of a http request:

const response = await fetch(url, {
  method: "PUT", 
  mode: "no-cors",
  cache: "no-cache",
  credentials: "same-origin", 
  headers: {
    "Content-Type": "application/json",
  },
  redirect: "follow", 
  referrerPolicy: "no-referrer", 
  body: JSON.stringify(subscription), 
});

but I found no matter what I do, I always lose the extra property inside subscription in the process of JSON.stringify().

I know the cause: it's because that the properties in extra object are not enumerable.

So far, I have tried:

1.use the spread:

newSub = {
  ...subscription,
  ...extra
}

but the content of newSub will be exactly same with extra, the properties of subscription are all lost.

2.add toJSON function into the place where I generate the extra object

getExtra() : {    
.......
return {
      city: ipObject.city,
      country: ipObject.country_name,
      ip: ipObject.ip,
      lat: ipObject.latitude,
      lng: ipObject.longitude,
      org: ipObject.org,
      postal: ipObject.postal,
      region: ipObject.region,
      toJSON: () => {
        return this;
      }
    };    
}

no effect at all.

I attach my code here:

async function updateSubscription() {
  try {
    const allowed = await askForPermission();
    if (!allowed) return;

    let subscription = await getSubscription();
    if (!subscription) return;

    // email
    const email = getEmail();
    if (!email || !validateEmail(email)) {
      alert("huh...so how are you going to receive notifications?");
      return;
    }

    // ip
    let ipObject = await getIP();
    let extra = {};
    if (ipObject) {
      ipObject.email = email;
      extra = ipObject;
    } else {
      extra.email = email;
    }

    console.log("extra: ", extra);

    // var newSubscription = Object.assign({}, subscription, {extra});
    // const newSubscription = {
    //   ...subscription,
    //   extra
    // };
    let newSubscription = subscription;
    newSubscription["extra"] = extra;
    console.log("new subscription1: ", newSubscription);
    console.log("new subscription1 stringified: ", JSON.stringify(newSubscription));

    const successful = await saveRegistration(newSubscription);

    if (successful) alert("you have successfully subscribed to the DC monitor");
    else alert("shit happens, try it later");
  } catch (err) {
    console.log("updateSubscription() failed: ", err);
  }
}

async function getSubscription() {
  console.log("try to get subscription");
  try {
    const swRegistration = await navigator.serviceWorker.ready;
    const pushSubscription = await swRegistration.pushManager.getSubscription();
    console.log("pushSubscription: ", pushSubscription);
    return pushSubscription;
  } catch (error) {
    console.log("getSubscription() error: ", error);
    return null;
  }
}

Update

1.Tried 1 more approach:

var newSubscription = Object.assign({}, subscription, {extra});
console.log("subscription: ", newSubscription);
console.log("subscription stringified: ", JSON.stringify(newSubscription));

here is the output screenshot:

enter image description here

2.Also this one:

 const newSubscription = {
      ...subscription,
      extra
    };
    console.log("new subscription: ", newSubscription);
    console.log("new subscription stringified: ", JSON.stringify(newSubscription));

And here is the screenshot of output:

enter image description here

3.with string index approach:

let newSubscription = subscription;
    newSubscription["extra"] = extra;
    console.log("new subscription1: ", newSubscription);
    console.log("new subscription1 stringified: ", JSON.stringify(newSubscription));

enter image description here


Solution

  • If mutating subscription is OK, you can just use:

    subscription['extra'] = extra;
    

    If you want a new object, you can use:

    const subscriptionObject = Object.assign({}, subscription, { extra });
    

    EDIT: Since you are working with the Push API, the properties in PushSubscription are not enumerable. So the subscription object does not behave quite like a normal object, which is why the suggested approaches have not been working.

    However, you can serialize the push subscription using PushSubscription.toJSON() first to serialize it to a "normal" object, then use one of the suggested techniques:

    subscriptionObject = Object.assign({}, subscription.toJSON(), { extra });