[英]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.