C#浮点序列化到JavaScript数字的兼容问题及自定义转换器疑问
我用Newtonsoft.Json序列化自定义类作为Web请求的Payload,通过哈希校验保证数据一致性。序列化代码是JsonConvert.SerializeObject(_report,settings);,配置如下:
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; }
待序列化的类示例:
public class FlightLogEntry { public long timestamp; public Vector3 position; public float speedX; public float speedZ; public float speedMagnitude; public float battery; public FlightLogEntry() { } }
C#序列化后,整数型浮点数会保留.0(比如0.0),但后端JavaScript用JSON.stringify处理后,这些值会变成整数(比如0),导致两边哈希值不一致。之前试过用自定义转换器去掉.0解决哈希问题,但备份文件反序列化时,JSON里的整数无法转成float类型,引发错误。
问题解答
1. 处理C#浮点数1.0在JavaScript中变为1的兼容问题的更优方案
有两种可靠的思路:
思路一:统一序列化规则,让C#输出和JS一致的JSON
实现一个自定义JsonConverter<float>,序列化时把整数型浮点数(比如0.0、1.0)输出为整数,非整数型浮点数正常输出;反序列化时同时兼容整数和浮点数格式的JSON值。这样C#和JS序列化结果完全一致,哈希校验就能通过,同时反序列化也不会出错。
示例转换器代码:
public class FloatConverter : JsonConverter<float> { public override void WriteJson(JsonWriter writer, float value, JsonSerializer serializer) { // 判断是否是整数型浮点数 if (value == Math.Floor(value)) { writer.WriteValue((long)value); } else { writer.WriteValue(value); } } public override float ReadJson(JsonReader reader, Type objectType, float existingValue, bool hasExistingValue, JsonSerializer serializer) { // 兼容整数和浮点数类型的JSON值 if (reader.TokenType == JsonToken.Integer) { return Convert.ToSingle((long)reader.Value); } else if (reader.TokenType == JsonToken.Float) { return Convert.ToSingle(reader.Value); } throw new JsonSerializationException($"Unexpected token type {reader.TokenType} for float value"); } }
然后在GetJsonSettings里添加转换器:
settings.Converters.Add(new FloatConverter());
思路二:在C#端模拟JS的JSON.stringify行为计算哈希
如果不想修改序列化输出,可以在C#计算哈希前,先把序列化后的JSON做一次“JS风格”的处理——把所有\.0(?=[,}\]])格式的字符串替换成空,再计算哈希。这样计算出的哈希和JS端基于JSON.stringify结果计算的哈希一致,同时不影响备份文件的反序列化(因为原始序列化结果保留了.0)。
示例处理代码:
string csharpJson = JsonConvert.SerializeObject(_report, settings); // 替换整数型浮点数的.0 string jsStyleJson = Regex.Replace(csharpJson, @"\.0(?=[,}\]])", ""); // 基于jsStyleJson计算哈希 byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(jsStyleJson)); string hash = Convert.ToHexString(hashBytes);
2. 自定义转换器的ReadJson方法中识别目标类型
完全可以。自定义转换器的ReadJson方法参数里的objectType就是当前要反序列化的目标类型(比如typeof(float)),你可以根据这个类型来处理不同的JSON值:
比如在通用转换器中:
public class NumberConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(float) || objectType == typeof(double); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Integer) { if (objectType == typeof(float)) { return Convert.ToSingle((long)reader.Value); } else if (objectType == typeof(double)) { return Convert.ToDouble((long)reader.Value); } } else if (reader.TokenType == JsonToken.Float) { return Convert.ChangeType(reader.Value, objectType); } throw new JsonSerializationException($"Cannot convert token {reader.TokenType} to {objectType}"); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 序列化逻辑按需实现 var floatValue = Convert.ToSingle(value); if (floatValue == Math.Floor(floatValue)) { writer.WriteValue((long)floatValue); } else { writer.WriteValue(floatValue); } } }
如果是泛型转换器(比如JsonConverter<float>),objectType固定为typeof(float),直接处理即可,就像第一个问题里的示例转换器那样。
内容的提问来源于stack exchange,提问作者StewVanB

