13

What are the best practices passing dollar amounts in JSON?

Passing the amounts as strings, or floats? or other?

We are a bit worried about round off errors displayed on client (iOS / Android / Web), or possibly different values displayed on one client compared to another.

Mary Rogers
  • 262
  • 2
  • 8

4 Answers4

14

Effective Java, 2nd Edition Joshua Bloch said

The float and double types are particularly ill-suited for monetary calculations because it is impossible to represent 0.1 (or any other negative power of ten) as a float or double exactly.

For example, suppose you have $1.03 and you spend 42c. How much money do you have left?

System.out.println(1.03 - .42); prints out 0.6100000000000001.

The right way to solve this problem is to use BigDecimal, int or long .

Never hold monetary values in a float variable. Floating point is not suitable for currency use either fixed-point or decimal values.

Better if provide the currency code and the same value in a few different formats. Consider this typical response for a amount of 0.00234

"amount": {
  "currency": "USD",
  "decimal": 0.00234,
  "integer": 234000,
  "integer_scale": 8,
  "pretty": "\u0e3f 0.00234 BTC",
  "string": "0.00234"
}

You have the option of using any of the provided number formats.

decimal: This is a decimal number string: Same as decimal but quoted so your JSON library thinks it is a string. pretty: A string ready to be shown to users. Includes appropriate currency sign and currency code. currency: The 3-letter code for the currency.

Following are two examples API :

1 - Common Currency Codes in JSON

{
    "USD": {
        "symbol": "$",
        "name": "US Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "USD",
        "name_plural": "US dollars"
    },
    "CAD": {
        "symbol": "CA$",
        "name": "Canadian Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "CAD",
        "name_plural": "Canadian dollars"
    },
    "EUR": {
        "symbol": "€",
        "name": "Euro",
        "symbol_native": "€",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "EUR",
        "name_plural": "euros"
    }
}

https://gist.github.com/Fluidbyte/2973986

2 - Fixer API

http://api.fixer.io/latest

{
    "base": "EUR",
    "date": "2017-07-28",
    "rates": {
        "AUD": 1.4732,
        "BGN": 1.9558,
        "BRL": 3.7015,
        "CAD": 1.4712,
        "CHF": 1.1357,
        "CNY": 7.9087,
        "CZK": 26.048,
        "DKK": 7.4364,
        "GBP": 0.89568,
        "HKD": 9.1613,
        "HRK": 7.412,
        "HUF": 304.93,
        "IDR": 15639,
        "ILS": 4.1765,
        "INR": 75.256,
        "JPY": 130.37,
        "KRW": 1317.6,
        "MXN": 20.809,
        "MYR": 5.0229,
        "NOK": 9.3195,
        "NZD": 1.5694,
        "PHP": 59.207,
        "PLN": 4.2493,
        "RON": 4.558,
        "RUB": 69.832,
        "SEK": 9.5355,
        "SGD": 1.5947,
        "THB": 39.146,
        "TRY": 4.1462,
        "USD": 1.1729,
        "ZAR": 15.281
    }
}

What is the standard for formatting currency values in JSON?

Jean-Rémy Revy
  • 5,607
  • 3
  • 39
  • 65
vaquar khan
  • 10,864
  • 5
  • 72
  • 96
9

I think one solution is to pass the amount times 100 as an integer

  • $100 --> 10000
  • $1.5 --> 150
  • $19.99 --> 1999

(No rounding errors, safe storage in database, unless you need more decimals if you use this in currency exchange market for example).

That way you can manipulate your amounts (addition or multiplication,..), and to display it juste divide again by 100.

Majid Laissi
  • 19,188
  • 19
  • 68
  • 105
  • 2
    Exactly. If you think you need floating point you probably don't understand scaling. It is a uniquely terrible idea for money! Remember "back in the day" C didn't even have floating point, which didn't matter much because the [CPUs](https://en.wikipedia.org/wiki/Intel_8087) couldn't really do it anyway. – Terry Jul 20 '17 at 18:51
  • This won't solve the problem, just adding some noise when the amount shall be displayed – deblocker Jul 30 '17 at 15:59
  • 1
    @deblocker What kind of noise does this add? I think this satisfies the requirements posed in the original question just fine. It allows for an exact value to be passed around and then the client can handle displaying it however it needs to. It also makes it easy to deal with internationalization and localization. Also this is how Strip handles currency in their API. – Chris Baldwin Jul 30 '17 at 18:28
  • @ChrisBaldwin: because JSON doesn't differentiate between integers and float, the same way it doesn't differentiate between currency and float. So it simply sounds nonsense for me, to say: "pass it as integer". The only way to specify a sub-type is to add an additional parameter in the JSON schema, something like: `{type: integer, value: 1000}`. But the interpretation of this attribute would be proprietary. – deblocker Jul 31 '17 at 06:52
  • "pass it as integer" just means write it as you would write an integer value: only digits and no decimal points or exponents. It does not mean that you should declare it as an integer in JSON. – Majid Laissi Jul 31 '17 at 14:53
  • @MajidLaissi: `(1.40 - 1.00)*100` doesn't solve the floating point issue, and you cannot deal money with just only two decimals, as there are needed at least six significant digits. Nothing against you, but honestly, i'm not able to understand how you proposal as-is can solve anything. It would be nice if you could add further explanation. – deblocker Aug 01 '17 at 06:57
  • @deblocker the chosen value 100 in my example means that you cannot make operations for values less then a cent. Your example operation deals with a value of 1.4 cents which is not an allowed value in my use case (and most use cases). If you need more decimals, just make a bigger scale instead of just 100. – Majid Laissi Aug 02 '17 at 00:06
0

JSON does not have distinct types for integers and floating-point values. Therefore, JSON Schema can not use type alone to distinguish between integers and non-integers.

Hence, you can try something like this.

var jsonObj = [{
"amount": "$1234.46"
},{
"amount": "$2234.56"
},{
"amount": "$3234.66"
}];

for (var i in jsonObj) {
  jsonObj[i].amount = '$'+Math.round(jsonObj[i].amount.substring(1,jsonObj[i].amount.length));
}

console.log(jsonObj);

Hope, it will work as per your expectation. Thanks

Debug Diva
  • 26,058
  • 13
  • 70
  • 123
-4

I dont think there is a 'best practice' around this.

My recommendation, however, would be to encode them as floats so that you don't intermix ways of displaying dollars. As an example, you would want to avoid possibly passing

{ "dollars": "1,000$" }
{ "dollars": "1000.00$" }
{ "dollars": "1000.00" }
{ "dollars": "$1000.00" }

A simpler way to represent dollars is using floats. The precision can vary, which can be a good thing

{ "dollars": 1000.00 }
{ "dollars": 0.001231231 }

Watch out for the case where 0.00 renders as 0.001 due to rounding

{ "dollars": 0.005 }
Belfordz
  • 630
  • 6
  • 13
  • 1
    i'm not sure using floats is the best idea. A 1.0f on a system can be rendered as 0.9999999999f in another. – Majid Laissi Jul 20 '17 at 18:54
  • 1
    Agreed, but you can think of them as just Real Numbers until you try to use them in what ever language. Once you parse the JSON into something, you will always need to handle floating point errors. – Belfordz Jul 20 '17 at 22:09
  • @MajidLaissi: as i am truly interested in this topic, could you please kindly provide a reproducible example of 1.0f rendered as 0.9999999999f ? – deblocker Aug 01 '17 at 07:07
  • imagine having your system add 1/3 to 1/9, the answer is obviously 1 but on some systems it may be showed as 0.999999 or 1.0000001 instead of 1. https://home.wordpress.com/2013/07/31/float-and-double-13-23-0-99999999/ – Majid Laissi Aug 02 '17 at 00:02