对于.Net中的浮点值,如何防止未定义的值序列化为0?

时间:2016-01-06 01:30:59

标签: c# json serialization json.net

我有一个相当大的对象类,它由一堆原始属性值(int,float,bool,string)定义。我从客户端应用程序获取对象作为json字符串,我将其反序列化为C#.Net类,以便我可以将它们保存到SQL数据库。我遇到的问题是序列化程序为浮点参数提供了一个默认值0,这会破坏我的应用程序,因为未定义的值需要以不同于值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 原来我是个白痴。如果在类定义中声明它们应该可以为空,则反序列化器的自动魔术会将值保留为null。我需要做的就是改变我的问题

public bool r3_verified { get; set; }

public bool? r3_verified { get; set; }

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

感谢@dbc指出我正确的方向。

2 个答案:

答案 0 :(得分:3)

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

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

设置NullValueHandling.Ignore会阻止在不存在时将属性序列化为JSON。也可以通过设置JsonSerializerSettings.NullValueHandling来完成此操作,从而避免将属性添加到每个属性。

另一种可能性(我不建议)是定义一个特殊的“sentinal”常量值,表示未定义的浮点值;然后在每个float上设置属性[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
}

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

  

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

因此,您的sentinal可能会稍微变圆,并且似乎成为初始值。

答案 1 :(得分:0)

你可以扩展JsonConverter来做你想做的任何事情。

在此示例中,如果收到的值为float.NaNnull,我将返回undefined

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);
    }
}