Preserving undefined that JSON.stringify otherwise removes
Asked Answered
A

8

125

How do I preserve undefined values when doing JSON.stringify(hash)?

Here's an example:

var hash = {
  "name" : "boda",
  "email" : undefined,
  "country" : "africa"
};

var string = JSON.stringify(hash);

// > '{"name":"boda","country":"africa"}'

Email disappeared from JSON.stringify.

Arceliaarceneaux answered 24/10, 2014 at 2:46 Comment(5)
JSON does not have an undefined value. What do you want?Blaylock
You would have to change the value to the string "undefined" or perhaps null or an empty string "". How would you prefer it?Brooks
Check out json.org Note that in the standard value can be string, number, object, array, true, false or null. Note that undefined isn't an option.Baseman
PS: in a JS object literal, you do not need to quote keys whose values are legal identifiers. I.e. { name:"boda", email:undefined, country:"africa" }Marketplace
Africa is not a country :PMetonymy
M
193

The JSON spec does not allow undefined values, but does allow null values.

You can pass a replacer function to JSON.stringify to automatically convert undefined values to null values, like this:

var string = JSON.stringify(
  obj,
  function(k, v) { return v === undefined ? null : v; }
);

This works for undefined values inside arrays as well, as JSON.stringify already converts those to null.

Metonymy answered 24/8, 2015 at 10:22 Comment(8)
ES6: JSON.stringify(obj, (k, v) => v === undefined ? null : v)Trihedral
while this is a solution it does not solve the issue as it does not preserve the value as undefined, it changes it to null.Travelled
@Travelled JSON only allows null values, there is no undefined value defined in the JSON standard.Metonymy
@Metonymy yes i know but this still does not solve the problem.Travelled
@Travelled Is the issue that you think the JSON spec should be changed to allow undefined values? Or do you not need valid JSON, but something else?Metonymy
@Metonymy i posted a solution that will keep undefined intact.Travelled
You can also do this with other non-displayable values such as functions return typeof v === 'function' ? 'func' : v;Houle
If you want to really preserve undefined values, please look at https://mcmap.net/q/179742/-preserving-undefined-that-json-stringify-otherwise-removesFranzen
E
12

This should do the trick

// Since 'JSON.stringify' hides 'undefined', the code bellow is necessary in
// order to display the real param that have invoked the error.
JSON.stringify(hash, (k, v) => (v === undefined) ? '__undefined' : v)
    .replace(/"__undefined"/g, 'undefined')
Everick answered 30/4, 2018 at 12:4 Comment(4)
I believe it's better to replace over regexp: JSON.stringify(hash, (k, v) => (v === undefined) ? '__undefined' : v) .replace(/\"__undefined\"/g, 'undefined') because your example will replace only first matchScintillator
+1 on using the Regex. If you do so, note that there's no need to escape ": instead of replace(/\"__undefined\"/g, 'undefined') you can do replace(/"__undefined"/g, 'undefined'). Alternatively nowadays you can use replaceAll().Mercuric
The problem with this code is that it will fail to work correctly for strings that happen to contain "__undefined". Also, it produces invalid JSON that cannot be parsed by JSON.parseMetonymy
I dont understand why use "__undefined" only to replace it later. JSON.stringify(hash, (k, v) => (v === undefined) ? 'undefined' : v) would this not work the same way?Omnibus
M
12

You can preserve the key by converting to null since a valid JSON does not allow undefined;

Simple one liner:

JSON.stringify(obj, (k, v) => v === undefined ? null : v)
Muir answered 18/7, 2018 at 7:26 Comment(4)
What if I want to make it recursive?Fruiter
"You can preserve it by changing it" ;)Goldi
If this is part of an API and your codding style is if ( o.prop == undefined ) ..., then you can say "preserve" the semantics, because only null == undefined.Noddy
I think we can replace 'k' with '_' to indicate an unused parameterBasal
T
7
function stringifyWithUndefined(value: any, space: number): string {
  const str = JSON.stringify(
    value,
    (_k, v) => v === undefined ? '__UNDEFINED__' : v,
    space
  );
  return str.replaceAll('"__UNDEFINED__"', 'undefined');
}

Example 1:

const object = {
  name: 'boda',
  email: undefined,
  country: 'africa'
};
console.log(stringifyWithUndefined(object, 2));

Result (string):

{
  "name": "boda",
  "email": undefined,
  "country": "africa"
}

Example 2:

const array = [object, { object }, [[object]]];
console.log(stringifyWithUndefined(array, 2));

Result (string):

[
  {
    "name": "boda",
    "email": undefined,
    "country": "africa"
  },
  {
    "object": {
      "name": "boda",
      "email": undefined,
      "country": "africa"
    }
  },
  [
    [
      {
        "name": "boda",
        "email": undefined,
        "country": "africa"
      }
    ]
  ]
]



Note that undefined is not valid JSON and JSON.parse() will fail with SyntaxError: Unexpected token [...] is not valid JSON if you give it the result of stringifyWithUndefined()

Trautman answered 23/2, 2018 at 22:43 Comment(3)
I might extend this to do the same for NaN. Using repl that overwrites console.log with JSON.stringify, and losing those valuesMilkweed
I needed it to do NaN in addition to undefined as well. In case anyone else does in the future: function customStringify(o) { return JSON.stringify(o, (k, v) => v === undefined ? '__undefined__' : v !== v ? '__NaN__' : v).replace(/"__undefined__"/g, 'undefined').replace(/"__NaN__"/g, 'NaN'); }Barilla
There are two problems with this code. First, it will fail to work correctly if the object happens to contain a string that contains "__undefined__". Secondly, it will fail to produce valid JSON that can be parsed by JSON.parse.Metonymy
M
5

Use null instead of undefined.

var hash = {
  "name" : "boda",
  "email" : null,
  "country" : "africa"
};

var string = JSON.stringify(hash);

> "{"name":"boda","email":null,"country":"africa"}"
Marela answered 24/10, 2014 at 2:59 Comment(1)
This is not answering the question.Boggess
T
4

Im reading between the lines here and guessing that you want to have the value undefined when you use JSON.parse?

If that is the case you could use the following:

var encodeUndefined = function(obj, undefinedPaths, path) {
  path = path || 'ROOT';
  for (var key in obj) {
    var keyPath = path + '.' + key;
    var value = obj[key];
    if (value === undefined) {
      undefinedPaths.push(keyPath);
    } else if (typeof value == "object" && value !== null) {
      encodeUndefined(obj[key], undefinedPaths, keyPath);
    }
  }
}

var stringifyAndPreserveUndefined = function(obj) {
  var undefinedPaths = [];
  //save all paths that have are undefined in a array.
  encodeUndefined((obj), undefinedPaths);
  return JSON.stringify({
    ROOT: obj,
    undefinedPaths: undefinedPaths
  }, function(k, v) { if (v === undefined) { return null; } return v; });
}

var parseAndRestoreUndefined = function(value) {
  var data = JSON.parse(value);
  var undefinedPaths = data.undefinedPaths;
  var obj = data.ROOT;
  //Restore all undefined values
  for (var pathIndex = 0; pathIndex < undefinedPaths.length; pathIndex++) {
    var pathParts = undefinedPaths[pathIndex].substring(5).split('.');
    var item = obj;
    for (var pathPartIndex = 0; pathPartIndex < pathParts.length - 1; pathPartIndex++) {
      item = item[pathParts[pathPartIndex]];
    }
    item[pathParts[pathParts.length - 1]] = undefined;
  }
  return obj;
}

var input = {
  test1: 'a',
  test2: 'b',
  test3: undefined,
  test4: {
    test1: 'a',
    test2: undefined
  }
};
var result = stringifyAndPreserveUndefined(input);
var result2 = parseAndRestoreUndefined(result);

stringifyAndPreserveUndefined will encode all undefined values in a array and when you call parseAndRestoreUndefined it will put them in the correct place again.

The one downside is the json will not look exactly like the object. In the example above it will turn into {"ROOT":{"test1":"a","test2":"b","test4":{"test1":"a"}},"undefinedPaths":["ROOT.test3","ROOT.test4.test2"]}

Travelled answered 29/11, 2017 at 9:39 Comment(2)
Long solution, but it works at least. (in my case, I just use the "convert to null" approach, since I don't need it preserved-in-json as undefined)Hairpiece
Why the downvote? is there a usecase where this does not work? if that is the case please provide the data so i can correct the function!Travelled
F
0

JSON does not have an undefined value, but we could write a workaround:

Preserving nested undefined values

I wrote 2 functions that internally uses JSON.stringify and JSON.parse and preserves nested undefined values using a value placeholder:

Equivalent to JSON.stringify:

/**
 * Serialize a POJO while preserving nested `undefined` values.
 */
function serializePOJO(value, undefinedPlaceholder = "[undefined]") {
  const replacer = (key, value) => (value === undefined ? undefinedPlaceholder : value);
  return JSON.stringify(value, replacer);
}

Equivalent to JSON.parse:

/**
 * Deserialize a POJO while preserving nested `undefined` values.
 */
function deserializePOJO(value, undefinedPlaceholder = "[undefined]") {
  const pojo = JSON.parse(value);
  if (pojo === undefinedPlaceholder) {
    return undefined;
  }

  // Function that walks through nested values
  function deepIterate(value, callback, parent, key) {
    if (typeof value === "object" && value !== null) {
      Object.entries(value).forEach(([entryKey, entryValue]) => deepIterate(entryValue, callback, value, entryKey));
    } else if (Array.isArray(value)) {
      value.forEach((itemValue, itemIndex) => deepIterate(itemValue, callback, value, itemIndex));
    } else if (parent !== undefined) {
      callback(value, parent, key);
    }
  }

  // Replaces `undefined` placeholders
  deepIterate(pojo, (value, parent, key) => {
    if (value === undefinedPlaceholder) {
      parent[key] = undefined;
    }
  });

  return pojo;
}

Usage:

const source = {
    foo : undefined,
  bar : {
   baz : undefined
  }
};

const serialized = serializePOJO(source);
console.log("Serialized", serialized);
// '{"foo":"[undefined]","bar":{"baz":"[undefined]","qux":[1,"[undefined]",2]}}'

const deserialized = deserializePOJO(serialized);
console.log("Deserialized", deserialized);

Works with both object entries and array items.

The downside is that you have to choose an appropriate placeholder that will not be mistaken via a "real" source value. The placeholder is customizable via the optional undefinedPlaceholder argument.

This is specially useful to store POJO in browser local storage ;)

See also:

Franzen answered 12/5, 2022 at 13:26 Comment(0)
E
-2

This will cause it to print as undefined but this is INVALID json, but is valid JavaScript.

var string = JSON.stringify(obj, function(k,v){return v===undefined?"::undefined::":v}, 2).replace(new RegExp("\"::undefined::\"", 'g'), "undefined");
Estis answered 17/9, 2019 at 18:14 Comment(1)
The other problem with this code is that it will fail to work as expected if a string happens to contain "::undefined::"Metonymy

© 2022 - 2024 — McMap. All rights reserved.