Json.Net 序列化为自定义转换器提供了错误的属性



>我有这个模型,我想使用 Json.Net 进行序列化和反序列化:

public struct RangeOrValue
{
public int Value { get; }
public int Min { get; }
public int Max { get; }
public bool IsRange { get; }
public RangeOrValue(int min, int max)
{
Min = min;
Max = max;
IsRange = true;
Value = 0;
}
public RangeOrValue(int value)
{
Min = 0;
Max = 0;
Value = value;
IsRange = false;
}
}

我对序列化有特殊要求。如果使用第一个构造函数,则应将该值序列化为{ "Min": <min>, "Max": <max> }。 但是如果使用第二个构造函数,则值应序列化为<value>

例如,new RangeOrValue(0, 10)需要序列化为{ "Min": 0, "Max": 10 }new RangeOrValue(10)需要序列化为10

我编写了这个自定义转换器来完成此任务:

public class RangeOrValueConverter : JsonConverter<RangeOrValue>
{
public override void WriteJson(JsonWriter writer, RangeOrValue value, JsonSerializer serializer)
{
if (value.IsRange)
{
// Range values are stored as objects
writer.WriteStartObject();
writer.WritePropertyName("Min");
writer.WriteValue(value.Min);
writer.WritePropertyName("Max");
writer.WriteValue(value.Max);
writer.WriteEndObject();
}
else
{
writer.WriteValue(value.Value);
}
}
public override RangeOrValue ReadJson(JsonReader reader, Type objectType, RangeOrValue existingValue, bool hasExistingValue, JsonSerializer serializer)
{
reader.Read();
// If the type is range, then first token should be property name ("Min" property)
if (reader.TokenType == JsonToken.PropertyName)
{
// Read min value
int min = reader.ReadAsInt32() ?? 0;
// Read next property name
reader.Read(); 
// Read max value
int max = reader.ReadAsInt32() ?? 0;
// Read object end
reader.Read();
return new RangeOrValue(min, max);
}
// Read simple int
return new RangeOrValue(Convert.ToInt32(reader.Value));
}
}

为了测试功能,我编写了这个简单的测试:

[TestFixture]
public class RangeOrValueConverterTest
{
public class Model
{
public string Property1 { get; set; }
public RangeOrValue Value { get; set; }
public string Property2 { get; set; }
public RangeOrValue[] Values { get; set; }
public string Property3 { get; set; }
}
[Test]
public void Serialization_Value()
{
var model = new Model
{
Value = new RangeOrValue(10),
Values = new[] {new RangeOrValue(30), new RangeOrValue(40), new RangeOrValue(50),},
Property1 = "P1",
Property2 = "P2",
Property3 = "P3"
};
string json = JsonConvert.SerializeObject(model, new RangeOrValueConverter());
var deserializedModel = JsonConvert.DeserializeObject<Model>(json, new RangeOrValueConverter());
Assert.AreEqual(model, deserializedModel);
}
}

当我运行测试时,对象序列化成功。但是当它尝试反序列化它时,我收到此错误:

Newtonsoft.Json.JsonReaderException : Could not convert string to integer: P2. Path 'Property2', line 1, position 46.

堆栈跟踪导致行int min = reader.ReadAsInt32() ?? 0;

我认为我在转换器中做错了什么,导致 Json.Net 向转换器提供错误的值。但我不太清楚。有什么想法吗?

您的基本问题是,在ReadJson()开始时,您无条件地调用Read()以使读者超越当前令牌:

public override RangeOrValue ReadJson(JsonReader reader, Type objectType, RangeOrValue existingValue, bool hasExistingValue, JsonSerializer serializer)
{
reader.Read(); 

但是,如果当前标记是对应于具有单个值的RangeOrValue的整数,那么您刚刚跳过了该值,使读取器位于接下来的任何内容上。 相反,当当前值的类型为JsonToken.Integer时,您需要处理该值。

话虽如此,您的转换器还有其他几个可能的问题,主要与您假设传入的 JSON 采用特定格式而不是验证这一事实有关:

  • 根据 JSON 标准,对象是一组无序的名称/值对,但ReadJson()假定特定的属性顺序。

  • ReadJson()不会跳过未知属性或出错。

  • ReadJson()不会在截断的文件上出错。

  • ReadJson()不会在意外的令牌类型(例如,数组而不是对象或整数)上出错。

  • 如果 JSON 文件包含注释(这些注释不包含在 JSON 标准中,但受 Json.NET 支持),则ReadJson()不会处理此问题。

  • 转换器不处理Nullable<RangeOrValue>成员。

    请注意,如果您继承自JsonConverter<T>,那么您将不得不为TNullable<T>编写单独的转换器。 因此,对于结构体,我认为从基类继承更容易JsonConverter

处理这些问题的JsonConverter如下所示:

public class RangeOrValueConverter : JsonConverter
{
const string MinName = "Min";
const string MaxName = "Max";
public override bool CanConvert(Type objectType)
{
return objectType == typeof(RangeOrValue) || Nullable.GetUnderlyingType(objectType) == typeof(RangeOrValue);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var range = (RangeOrValue)value;
if (range.IsRange)
{
// Range values are stored as objects
writer.WriteStartObject();
writer.WritePropertyName(MinName);
writer.WriteValue(range.Min);
writer.WritePropertyName(MaxName);
writer.WriteValue(range.Max);
writer.WriteEndObject();
}
else
{
writer.WriteValue(range.Value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.MoveToContent().TokenType)
{
case JsonToken.Null:
// nullable RangeOrValue; return null.
return null;  
case JsonToken.Integer:
return new RangeOrValue(reader.ValueAsInt32());
case JsonToken.StartObject:
int? min = null;
int? max = null;
var done = false;
while (!done)
{
// Read the next token skipping comments if any
switch (reader.ReadToContentAndAssert().TokenType)
{
case JsonToken.PropertyName:
var name = (string)reader.Value;
if (name.Equals(MinName, StringComparison.OrdinalIgnoreCase))
// ReadAsInt32() reads the NEXT token as an Int32, thus advancing past the property name.
min = reader.ReadAsInt32();
else if (name.Equals(MaxName, StringComparison.OrdinalIgnoreCase))
max = reader.ReadAsInt32();
else
// Unknown property name.  Skip past it and its value.
reader.ReadToContentAndAssert().Skip();
break;
case JsonToken.EndObject:
done = true;
break;
default:
throw new JsonSerializationException(string.Format("Invalid token type {0} at path {1}", reader.TokenType, reader.Path));
}
}
if (max != null && min != null)
return new RangeOrValue(min.Value, max.Value);
throw new JsonSerializationException(string.Format("Missing min or max at path {0}", reader.Path));
default:
throw new JsonSerializationException(string.Format("Invalid token type {0} at path {1}", reader.TokenType, reader.Path));
}
}
}

使用扩展方法:

public static partial class JsonExtensions
{
public static int ValueAsInt32(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType != JsonToken.Integer)
throw new JsonSerializationException("Value is not Int32");
try
{
return Convert.ToInt32(reader.Value, NumberFormatInfo.InvariantInfo);
}
catch (Exception ex)
{
// Wrap the system exception in a serialization exception.
throw new JsonSerializationException(string.Format("Invalid integer value {0}", reader.Value), ex);
}
}
public static JsonReader ReadToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
while (reader.Read())
{
if (reader.TokenType != JsonToken.Comment)
return reader;
}
throw new JsonReaderException(string.Format("Unexpected end at path {0}", reader.Path));
}
public static JsonReader MoveToContent(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
if (!reader.Read())
return reader;
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}

但是,如果您愿意支付轻微的性能损失,则可以通过序列化和反序列化 DTO 来简化转换器,如下所示,它使用相同的扩展方法类:

public class RangeOrValueConverter : JsonConverter
{
class RangeDTO
{
public int Min, Max;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(RangeOrValue) || Nullable.GetUnderlyingType(objectType) == typeof(RangeOrValue);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var range = (RangeOrValue)value;
if (range.IsRange)
{
var dto = new RangeDTO { Min = range.Min, Max = range.Max };
serializer.Serialize(writer, dto);
}
else
{
writer.WriteValue(range.Value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.MoveToContent().TokenType)
{
case JsonToken.Null:
// nullable RangeOrValue; return null.
return null;
case JsonToken.Integer:
return new RangeOrValue(reader.ValueAsInt32());
default:
var dto = serializer.Deserialize<RangeDTO>(reader);
return new RangeOrValue(dto.Min, dto.Max);
}
}
}

演示小提琴在这里显示两个转换器。

相关内容

  • 没有找到相关文章

最新更新