简体   繁体   English

如何编写 JsonConverter 来序列化和反序列化包含单个值的自定义结构?

[英]How to write a JsonConverter to serialize and deserialize a custom struct containing a single value?

I want to serialize and deserialize a custom struct.我想序列化和反序列化自定义结构。 The serializing seems to work, but deserializing it back doesn't work序列化似乎有效,但反序列化它不起作用

This is the struct :这是结构:

[JsonConverter(typeof(PriceConverter))]
public struct Price
{
    private readonly decimal _value;

    public Price(Price value)
    {
        _value = value._value;
    }

    public Price(decimal value)
    {
        _value = value;
    }

    public static implicit operator decimal(Price p)
    {
        return p._value;
    }

    public static implicit operator Price(decimal d)
    {
        return new Price(d);
    }
}

This is the JsonConverter :这是 JsonConverter :

class PriceConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(decimal?);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(reader) as Price?;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, (decimal)((Price)value));
    }
}

This is the class:这是课程:

public class Product
{
    public string Name { get; set; }
    public Price Price { get; set; }
}

This is some test code:这是一些测试代码:

Product product1 = new Product();
product1.Name = "Hans";
product1.Price = (decimal)2.3;

string priceSer = JsonConvert.SerializeObject(product1);

Product product2 = JsonConvert.DeserializeObject<Product>(priceSer);

product1 is correct. product1 是正确的。

?product1.Price

{MvcClient.Features.WFPBranch.Employee.Price}
    _value: 2.3

string priceSer contains : "{"Name":"Hans","Price":2.3}"字符串 priceSer 包含:"{"Name":"Hans","Price":2.3}"

product2 however isn't correct.但是 product2 不正确。 It should contain 2.3 but it contains 0. What is wrong ?它应该包含 2.3 但它包含 0。有什么问题? ... How can I make it contain 2.3? ...我怎样才能让它包含2.3?

?product2.Price

{MvcClient.Features.WFPBranch.Employee.Price}
    _value: 0

Your basic problem is that, in ReadJson() , serializer.Deserialize(reader) does not return what you think.您的基本问题是,在ReadJson()serializer.Deserialize(reader)不会返回您的想法。 We can demonstrate that by debugging as follows:我们可以通过如下调试来证明:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var v = serializer.Deserialize(reader);
    Console.WriteLine(v.GetType()); // prints System.Double
    return v as Price?;         
}

The value of v here turns out to be double not decimal .这里v的值原来是double而不是decimal And since your Price type does not have an implicit or explicit conversion from double , v as Price?而且由于您的Price类型没有从double隐式或显式转换, v as Price? returns a null nullable value.返回一个可为空的 null 值。

Demo fiddle #1 here .演示小提琴 #1在这里

Now, you might think you could resolve the problem by deserializing explicitly to decimal as follows:现在,您可能认为可以通过如下显式反序列化为decimal来解决问题:

class PriceConverter : JsonConverter<Price>
{
    public override Price ReadJson(JsonReader reader, Type objectType, Price existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        serializer.Deserialize<decimal>(reader);

    public override void WriteJson(JsonWriter writer, Price value, JsonSerializer serializer) =>
        serializer.Serialize(writer, (decimal)value);
}   

And sure enough, Price._value will be deserialized.果然, Price._value将被反序列化。 But in doing so, a subtle bug is introduced, namely that, at the time that ReadJson() is called, JsonTextReader will already have recognized the incoming value as a double and discarded the raw JSON token .但是这样做会引入一个微妙的错误,即在调用ReadJson()JsonTextReader已经将传入值识别为double并丢弃原始 JSON token Thus the additional precision of decimal will have been lost .因此, decimal的额外精度将丢失

Demo fiddle #2 here which shows that a price value of 2.30m is round-tripped as 2.3m . Demo fiddle #2 here显示2.30m的价格值被往返为2.3m

So what are your options to avoid this problem?那么你有什么选择来避免这个问题呢?

Firstly, you could serialize and deserialize globally with JsonSerializerSettings.FloatParseHandling = FloatParseHandling.Decimal , and also modify your converter to throw an exception in the event that the current value was already recognized to be double :首先,您可以使用JsonSerializerSettings.FloatParseHandling = FloatParseHandling.Decimal全局序列化和反序列化,并且还可以修改您的转换器以在当前值已被识别为double的情况下抛出异常:

class PriceConverter : JsonConverter<Price>
{
    public override Price ReadJson(JsonReader reader, Type objectType, Price existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        reader.ValueType == typeof(double) ? throw new JsonSerializationException("Double value for Price") : serializer.Deserialize<decimal>(reader);

    public override void WriteJson(JsonWriter writer, Price value, JsonSerializer serializer) =>
        serializer.Serialize(writer, (decimal)value);
}

var settings = new JsonSerializerSettings
{
    FloatParseHandling = FloatParseHandling.Decimal,
};
string priceSer = JsonConvert.SerializeObject(product1, settings);
Product product2 = JsonConvert.DeserializeObject<Product>(priceSer, settings);

Of course, this might have unexpected side-effects on how types containing double values are deserialized.当然,这可能会对如何反序列化包含double精度值的类型产生意想不到的副作用。

Demo fiddle #3 here .演示小提琴 #3在这里

Secondly, you could apply FloatParseHandlingConverter from this answer to Force decimal type in class definition during serialization to every class containing a Price and modify your converter to throw an exception as in the first option:其次,您可以将此答案中的FloatParseHandlingConverter应用于在序列化期间类定义中的强制十进制类型到每个包含Price类,并修改您的转换器以抛出异常,如第一个选项:

[JsonConverter(typeof(FloatParseHandlingConverter), FloatParseHandling.Decimal)]
public class Product
{
    public string Name { get; set; }
    public Price Price { get; set; }
}

This avoids the need to make global changes to serialization settings but might be somewhat burdensome if your Price type is used widely.这避免了对序列化设置进行全局更改的需要,但如果您的Price类型被广泛使用,可能会有些麻烦。

Demo fiddle #4 here .演示小提琴 #4在这里

Thirdly, if you are flexible about your JSON format , you could serialize Price as an object containing the price value, rather than a value:第三,如果您对 JSON 格式很灵活可以将Price序列化为包含价格值的对象,而不是一个值:

class PriceConverter : JsonConverter<Price>
{
    class PriceDTO { public decimal Value { get; set; } }

    public override Price ReadJson(JsonReader reader, Type objectType, Price existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        serializer.Deserialize<PriceDTO>(reader).Value;

    public override void WriteJson(JsonWriter writer, Price value, JsonSerializer serializer) =>
        serializer.Serialize(writer, new PriceDTO { Value = value });
}

This avoids the need to make any changes to FloatParseHandling outside the scope of Price , but does result in more complex JSON这避免了需要在Price范围之外对FloatParseHandling进行任何更改,但会导致更复杂的 JSON

{"Name":"Hans","Price":{"Value":2.30}}

Instead of代替

{"Name":"Hans","Price":2.30}

Demo fiddle #5 here .演示小提琴 #5在这里

Notes:笔记:

  • Your PriceConverter.CanConvert(Type objectType) returns true in the event that the incoming type is typeof(decimal?) , but it should instead check for typeof(Price) .如果传入类型是typeof(decimal?) ,您的PriceConverter.CanConvert(Type objectType)返回true ,但它应该检查typeof(Price)

    CanConvert() is not called when the converter is applied directly via attributes, however, so this mistake is not symptomatic.但是,当通过属性直接应用转换器时,不会调用CanConvert() ,因此此错误不是症状。

  • Alternatively, if you inherit from JsonConverter<Price> rather than JsonConverter , you won't have to override CanConvert() at all, and your code will be simpler and cleaner.或者,如果您从JsonConverter<Price>而不是JsonConverter ,则根本不必覆盖CanConvert() ,并且您的代码将更简单、更清晰。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用自定义 JsonConverter 反序列化复合列表 - How to deserialize composite list with custom JsonConverter 如何序列化和反序列化包含分隔符的嵌套xml - how to serialize and deserialize nested xml containing seperators 当值为空时,如何使用JsonConverter反序列化json? - How I can deserialize json using JsonConverter when the value is empty? JsonConverter如何反序列化为通用对象 - JsonConverter how to deserialize to generic object 在自定义JsonConverter中反序列化嵌套对象列表 - Deserialize nested object List in custom JsonConverter MongoDB 中序列化和反序列化结构的解决方法 - Workaround for Serialize and Deserialize struct in MongoDB 如何在 Unity 中序列化和反序列化包含 Gameobject 和 Vector2 的字典 - How to Serialize and Deserialize a Dictionary Containing Gameobject and a Vector2 in Unity ASP.NET:使用自定义类型的值序列化和反序列化哈希表 - ASP.NET : serialize and deserialize hashtable with custom type for value 如何将包含具有单个属性名称和值的对象数组的 JSON 反序列化为 c# model? - How to deserialize JSON containing an array of objects with a single property name and value each into a c# model? 将包含结构的类序列化为XML - Serialize to XML a class containing a struct
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM