我有一个要使用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向转换器提供错误的值。但我不太清楚。有什么想法吗?
答案 0 :(得分:1)
您的基本问题是,在ReadJson()
的开头,您无条件调用Read()
来使读者越过当前令牌:
public override RangeOrValue ReadJson(JsonReader reader, Type objectType, RangeOrValue existingValue, bool hasExistingValue, JsonSerializer serializer)
{
reader.Read();
但是,如果当前令牌是与具有单个值的RangeOrValue
相对应的整数,则您刚刚跳过了该值,使读者位于接下来的内容上。相反,当该值的类型为JsonToken.Integer
时,您需要处理 current 值。
话虽如此,您的转换器还有其他可能的问题,主要与您假设假设传入的JSON采用特定格式而不是验证这一事实有关。>
根据JSON standard,对象是一组无序的名称/值对 ,但是ReadJson()
假定特定的属性顺序。
ReadJson()
不会跳过过去或出现未知属性错误。
ReadJson()
在截断的文件上不会出错。
ReadJson()
在意外的令牌类型(例如,数组而不是对象或整数)上不会出错。
如果JSON文件包含注释(JSON标准中未包含注释,但Json.NET支持),则ReadJson()
将不会对此进行处理。
转换器不处理Nullable<RangeOrValue>
个成员。
请注意,如果您继承自JsonConverter<T>
,则必须为T
和Nullable<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);
}
}
}
演示小提琴,显示了两个转换器here。