Search code examples
javascriptc#jsonjson.net

How to handle serializing floats from C# into numbers in Javascript


I am using newtonsoft json to convert some custom classes into payloads for web requests. We are using a hashing system to ensure data concurrency.

I am using SerializeObject with custom settings: JsonConvert.SerializeObject(_report,settings);

This is the settings that I am using:

public static JsonSerializerSettings GetJsonSettings(){
    JsonSerializerSettings settings = new JsonSerializerSettings();
    settings.DefaultValueHandling = DefaultValueHandling.Include;
    settings.MissingMemberHandling = MissingMemberHandling.Ignore;
    settings.NullValueHandling = NullValueHandling.Ignore;
    settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
    settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    return settings;
}

When the serializer converts the object any floats are displayed with decimal points, including whole number floats. On the backend when js is reading the json payload when it converts floats to the js numbers any whole number floats lose their decimal point and trailing zeros:

This is one of the classes that is being serialized:

public class FlightLogEntry {
    public long timestamp;
    public Vector3 position;
    public float speedX;
    public float speedZ;
    public float speedMagnitude;
    public float battery;

    public FlightLogEntry() {

    }
}

1.02 => 1.02

Result of serialize in the program(C#):

{
    "timestamp": 230,
    "position": {
        "x": -15.07,
        "y": 2.009453,
        "z": -71.97
    },
    "speedX": 0.0,
    "speedZ": 0.0,
    "speedMagnitude": 0.0,
    "battery": 1379.98
}

1.0 => 1

Result of JSON.stringify(body) on the server(JS):

{
    "timestamp": 230,
    "position": {
        "x": -15.07,
        "y": 2.009453,
        "z": -71.97
    },
    "speedX": 0,
    "speedZ": 0,
    "speedMagnitude": 0,
    "battery": 1379.98
}

This was breaking our hash comparison checks, so what I did was make a custom converter to convert the floats on my end to trim the .0 on whole number floats. This corrected the hash mismatch but has introduced an issue on my end where when I write a backup to a file with the whole numbers trimmed, I cannot read them back as floats. They deserializer wants to read them as integers and throws an error.

Q: Is there a better way to handle the float 1.0 => 1 issue with js number?

Q: Is there a way to discern the "target" value in the ReadJson method of custom converters?

  • For example: If my json has a integer, but the target object class has a float, can I detect that a float is needed then make the cast manually in the JsonConverter.ReadJson method?

Solution

  • I think you can convert floats without decimal part to integers. This way you you JavaScript json will be the same as c# json string.You can try this custom converter

    var json = JsonConvert.SerializeObject(_report, new FloatJsonConverter());
    
    public class FloatJsonConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JObject o = JObject.FromObject(value);
            var floats = o.DescendantsAndSelf()
                        .Where(x => x.Type == JTokenType.Float)
                        .Select(x => (JProperty) x.Parent)
                        .ToList();
                        
            for (var i = 0; i < floats.Count(); i++)
            {
                var val = (float)floats[i];
                var intVal = (float)Math.Truncate(val);
                var decVal = Math.Abs((val - intVal));
                if (decVal <= 0.0000000000001) floats[i].Value = Convert.ToInt32(intVal);
            }
            o.WriteTo(writer);
        }
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
        }
    
        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    
        public override bool CanRead
        {
            get { return false; }
        }
    }
    

    you don't a special converter to deserialize int to float, since Newtonsoft.Json does it automatically.