7

I'm calling JSON.parse() to parse a JSON string which has small decimals.

The precision of the decimals is not being maintained after parsing. For example, a value like 3.1e-7 is being returned instead of the actual decimal.

How can I deserialize a JSON string in ng2+ while maintaining decimal precision?

UPDATE

I was thinking about mapping out the values from the string and then setting the values manually to the object after JSON.parse() but when I set a different small decimal number as a property value, the same number formatting occurs. So is this problem not necessarily unique to JSON.parse() but to Javascript in general? Or does JSON.parse() somehow configure property types in a fixed way?

user8570495
  • 1,645
  • 5
  • 19
  • 29
  • 3
    What is the actual decimal? What does the JSON object look like? – Hanlet Escaño Dec 21 '17 at 00:21
  • 1
    Please show your code. `(var foo = JSON.parse('{"foo":0.000000315453}'); console.log(foo.foo)` ... gives "3.15453e-7", but this might be different depending on context – Tibrogargan Dec 21 '17 at 00:23
  • for example, 0.00000017 is returned as 1.7e-7 – user8570495 Dec 21 '17 at 00:32
  • 3
    And `1.7e-7` is the actual representation of `0.00000017` – zerkms Dec 21 '17 at 00:32
  • There's no loss of decimal precision there. I opened the console on Chrome and typed in `.00000000000214124214` and it returned `2.14124214e-12` which is the [exponential form](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential) of the number. – Khauri Dec 21 '17 at 00:35
  • ok but is there maybe a formatting param that can be passed into JSON.parse() to keep the full decimal display representation? – user8570495 Dec 21 '17 at 00:37
  • Do you have any control over the original JSON object? `Number` is not the right type to store tiny numbers in. – AuxTaco Dec 21 '17 at 00:51
  • or can you describe how I could display the full decimal in an ng2 view like {{ toFullDecimal(myExponentialNumber) }} – user8570495 Dec 21 '17 at 00:52
  • @aux - the json string is from a third party. there is no Number type or any type specified in the json string and I'm guessing that JSON.parse() converts all decimals to numbers by default – user8570495 Dec 21 '17 at 00:55
  • 1
    https://stackoverflow.com/questions/1685680/how-to-avoid-scientific-notation-for-large-numbers-in-javascript – Kaiido Dec 21 '17 at 01:31
  • @AuxTaco it's not the actual value that matters, but number of significant digits. – zerkms Dec 21 '17 at 03:17

1 Answers1

9

As soon as you pass your JSON string through JSON.parse, you'll lose precision because of the way floating point math works. You'll need to store the number as an object designed for storing arbitrary-precision numbers, and you'll need to fiddle with the string itself before parsing it. The simplest way is with regexes. JSON is a context free grammar, and regexes work on regular grammars, so the warning applies:

WARNING: PARSING CFG WITH REGEX MAY SUMMON ZALGO

This regex should turn the numbers in your JSON into strings:

let stringedJSON = origJSON.replace(/:\s*([-+Ee0-9.]+)/g, ': "uniqueprefix$1"');

But I haven't tested it extensively and it definitely will screw things up if you have keys that are something like data:42.

Assuming it worked correctly, stringedJSON should now be something like {"foo": "uniqueprefix0.00000017", "bar": "an actual string"}. You can parse this with JSON.parse without losing precision, but uniqueprefix0.00000017 isn't what you want. JSON.parse can be called with an extra reviver argument, which transforms values passed to it before returning them. You can use this to convert your data back into a useful form:

let o = JSON.parse(stringedJSON, (key, value) => {
  // only changing strings
  if (typeof value !== 'string') return value;
  // only changing number strings
  if (!value.startsWith('uniqueprefix')) return value;
  // chop off the prefix
  value = value.slice('uniqueprefix'.length);
  // pick your favorite arbitrary-precision library
  return new Big(value);
});
AuxTaco
  • 4,883
  • 1
  • 12
  • 27
  • Thanks for that "summon Zalgo" link. Made my morning. – Iskandar Reza Dec 22 '17 at 17:29
  • As you mentioned, that regex matches numbers in strings like "data:42". It also fails to match any numbers that are not object values (I.E. array values or a top-level standalone number). This regex will work for all numbers in any JSON and skip all numbers that are part of a string: `replace( /((?:[^"]*?(?:"(?:[^"\\]|\\.)*?")?)*?)((?:[:,\[]|^)[\s\n]*)(-?(0|([1-9]\d*))(\.\d+)?([eE][-+]?\d*)?)/gsy, '$1$2"uniqueprefix$3"')` – Paul Mar 14 '19 at 16:22