【问题标题】:How do I prevent undefined values from serializing to 0 for float values in .Net?如何防止.Net中浮点值的未定义值序列化为0?
【发布时间】:2016-01-06 15:43:20
【问题描述】:

我有一个相当大的对象类,它由一堆原始属性值(int、float、bool、string)定义。我从客户端应用程序获取对象作为 json 字符串,我将其反序列化为 C# .Net 类,以便可以将它们保存到 SQL 数据库。我遇到的问题是序列化程序为浮点参数提供了一个默认值 0,这会破坏我的应用程序,因为未定义的值需要以不同于 0 的方式处理。(注意:如果用户定义了它们,则可以接受 0 值为 0,但我不能假设未定义的值是 0。)

我实际上有数百个这样的原始属性,所以我希望有一种方法可以使这项工作在全局范围内工作,而不必编写自定义属性类型对象。

以下是我将 JSON 字符串反序列化为 C# 对象的方法

using System.Web.Script.Serialization; // Note: used to deserialize JSON objects
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

RootObject obj = JsonConvert.DeserializeObject<RootObject>(JSONObjectFromClient);

这是我的对象类

public class SeatDefinition
{
public string DefinitionID { get; set; }
public string r3_tolType { get; set; }
public float r3_value { get; set; } // Note: this could be 0, but it shouldn't be assumed to be 0 if undefined
public bool r3_verified { get; set; }
public float r4_minus { get; set; } // same here
public float r4_plus { get; set; } // and here
public string r4_tolType { get; set; }
public float r4_value { get; set; } //etc
public bool r4_verified { get; set; }
public float r5_minus { get; set; }
public float r5_plus { get; set; }
public string r5_tolType { get; set; }
public float r5_value { get; set; }
public bool r5_verified { get; set; }
// ... 400 more such attributes
}

谁能帮忙?

编辑 2016-01-05 晚上 11:38 PST 原来我是个白痴。如果您在类定义中声明它们应该是可空的,反序列化器的自动魔法会将值保留为空。为了解决我的问题,我需要做的就是改变

public bool r3_verified { get; set; }

public bool? r3_verified { get; set; }

对于那些没有按我需要传入的值,我留下了空值。

感谢@dbc 为我指明了正确的方向。

【问题讨论】:

  • 您希望未定义输入的值是多少?
  • 如何识别未初始化的值?
  • 改为float?(等等)
  • 如果未定义比值应该是什么??

标签: c# json serialization json.net


【解决方案1】:

最自然的方法是将float 属性定义为nullables

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public float? r3_value { get; set; }

设置NullValueHandling.Ignore 可防止属性在不存在时被序列化为 JSON。也可以通过设置JsonSerializerSettings.NullValueHandling 来做到这一点,从而避免将属性添加到每个属性。

另一种可能性(我不太推荐)是定义一个特殊的“sentinal”常量值,表示一个未定义的浮点值;然后在每个浮点数上设置属性 [DefaultValue(Constants.UninitializedFloat)] 以通知 Json.NET 这个默认值;然后设置[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]表示,当JSON中缺少该属性时,应自动分配默认值:

public static class Constants
{
    public const double UninitializedFloat = float.MinValue;

    public static bool IsUninitialized(this float value)
    {
        return value == UninitializedFloat;
    }
}

public class SeatDefinition
{
    [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
    [DefaultValue(Constants.UninitializedFloat)]
    public float r3_value { get; set; } // Note: this could be 0, but it shouldn't be assumed to be 0 if undefined
}

我不建议这样做,因为如果意外序列化和反序列化,您的标记值可能不会往返。来自docs

如果涉及浮点数,则值可能不会往返。如果一个操作将原始浮点数转换为另一种形式,则称一个值往返,逆运算将转换后的形式转换回浮点数,并且最终的浮点数等于原始浮点数- 点数。往返可能会失败,因为在转换中丢失或更改了一个或多个最低有效数字。

因此,您的哨兵可能会略微四舍五入,并似乎成为一个初始化值。

【讨论】:

    【解决方案2】:

    您可以扩展 JsonConverter 来做几乎任何您想做的事情。

    在本例中,如果收到的值是 nullundefined,我将返回 float.NaN

    public class CustomFloatConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(float);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken token = JToken.Load(reader);
            if (token.Type == JTokenType.Float)
            {
                return (float)token;
            }
            if (token.Type == JTokenType.String)
            {
                return float.Parse(token.ToString(), CultureInfo.InvariantCulture);
            }
            if (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined)
            {
                return float.NaN;
            }
    
            throw new JsonSerializationException("Unexpected token type: " + token.Type);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    要使用新的自定义转换器,只需将其添加到应用初始化/引导的 JsonConvert 的默认设置中即可:

    JsonConvert.DefaultSettings = () => new JsonSerializerSettings
    {
        Converters = new List<JsonConverter> { new CustomFloatConverter() }
    };
    

    所以给定以下类:

    public class RootObject
    {
        public float MyValue { get; set; }
    }
    

    这是一个完整的测试上下文,所有测试都通过了:

    [TestClass]
    public class UnitTests
    {
        [TestInitialize]
        public void Setup()
        {
            JsonConvert.DefaultSettings = () => new JsonSerializerSettings
            {
                Converters = new List<JsonConverter> { new CustomFloatConverter() }
            };
        }
    
        [TestMethod]
        public void UndefinedIsTreatedAsNan()
        {
            RootObject obj = JsonConvert.DeserializeObject<RootObject>("{MyValue:undefined}");
    
            Assert.IsTrue(float.IsNaN(obj.MyValue));
        }
    
        [TestMethod]
        public void NullIsTreatedAsNaN()
        {
            RootObject obj = JsonConvert.DeserializeObject<RootObject>("{MyValue:null}");
    
            Assert.IsTrue(float.IsNaN(obj.MyValue));
        }
    
        [TestMethod]
        public void NumbersAreTreatedNormally()
        {
            RootObject obj1 = JsonConvert.DeserializeObject<RootObject>("{MyValue:1.23}");
            RootObject obj2 = JsonConvert.DeserializeObject<RootObject>("{MyValue:0.0}");
            RootObject obj3 = JsonConvert.DeserializeObject<RootObject>("{MyValue:\"1.23\"}");
    
            Assert.AreEqual(1.23f, obj1.MyValue);
            Assert.AreEqual(0, obj2.MyValue);
            Assert.AreEqual(1.23f, obj3.MyValue);
        }
    }
    

    【讨论】:

    • 我试过了,但没能成功。一切编译正确,但生成的反序列化对象对于我未传入的参数值仍然为 0.0。我怀疑 Setup 方法没有保留它的值,因为我的应用程序是一个安静的 Web 服务? (也许?我在这里有点超出我的深度)。看起来只需将属性设置为可为空就解决了我的问题,但非常感谢您的彻底和周到的回复。
    • @nickvans 在您的应用引导程序中设置一次。在你的情况下,因为它是一个 WS,它可能是 global.asax。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-09
    • 2014-06-14
    • 2011-05-29
    • 2012-05-24
    • 2014-08-08
    • 2021-02-20
    相关资源
    最近更新 更多