dart-lang/sdk

jsonEncode is converting `double` to `int` when used in Web

osaxma opened this issue · 2 comments

In the Web, jsonEncode is converting double to int when the fractional part is zero.

Given that Dart/Flutter is being used in multi-platform projects, this behavior becomes problematic for serializable data classes as shown in the example below:

// DartPad - Based on Flutter 1.26.0-17.8.pre Dart SDK 2.10.4
import 'dart:convert';

void main() {
  final data = Data(x: 3.0, y: 3.1);
  final jsonString = data.toJson(); 
  print(jsonString); // prints {"x":3,"y":3.1} 
  // works on web, throws in iOS/Android when trying to decode the printed string above. 
  final dataFromJson = Data.fromJson(jsonString); 
}

class Data {
  final double x;
  final double y;

  Data({this.x, this.y});

  Map<String, dynamic> toMap() {
    return {
      'x': x,
      'y': y,
    };
  }

  factory Data.fromMap(Map<String, dynamic> map) {
    if (map == null) return null;

    return Data(
      x: map['x'],
      y: map['y'],
    );
  }

  String toJson() => json.encode(toMap());

  factory Data.fromJson(String source) => Data.fromMap(json.decode(source));
}

Is there a way to keep the fractional part when converting a double to json?

lrhn commented

No.

On the web there is only one 1 value, not distinct integer 1 and double 1.0 values.
The JSON encoding does not provide a way to specify a format, so it has to pick a representation for the value 1 blindly. It choses 1 because that can still be parsed as both int (using int.parse) and double (using double.parse) at the other end.

The default JSON decoder will parse it as an int because that's what it does.

I recommend treating all JSON number values as having type num. The JSON format is a text format, it does not specify the meaning of a numeral. Each parser can do what it wants. So, when you get a number our, you should convert it to what you want (using .toDouble() if you want a double, or .toInt() if you want an int).
So:

return Data(
      x: map['x'].toDouble(),
      y: map['y'].toDouble(),
    );

Don't assume anything about the source or the representation, because the JSON might have been generated in a completely different language, or it might have been read and re-written by a third-party tool. All JSON promises is that it's a numeral.

Thank you @lrhn for the detailed response and recommendations.