我有以下测试代码:
[TestClass]
public class TestJsonDeserialize
{
public class MyClass
{
[JsonProperty("myint")]
public int MyInt { get; set; }
[JsonProperty("Mybool")]
public bool Mybool { get; set; }
}
[TestMethod]
public void Test1()
{
var errors = new List<string>();
var json1 = "{\"myint\":1554860000,\"Mybool\":false}";
var json2 = "{\"myint\":3554860000,\"Mybool\":false}";
var i = JsonConvert.DeserializeObject<MyClass>(json2, new JsonSerializerSettings
{
Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
{
Debug.WriteLine(args.ErrorContext.Error.Message);
errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
});
Assert.IsTrue(errors.Count <= 1);
}
}
Run Code Online (Sandbox Code Playgroud)
对 JsonConvert.DeserializeObject 的调用会产生 2 个错误。其中一个是预期的,但另一个不是。错误是:
尽管第一个错误被标记为已处理,但为什么会出现第二个错误。我已经从 Newtonsoft.Json 8.0.2 更新到 9.0.1 但它仍然存在。传递第一个字符串(json1 而不是 json2)时,根本不会发生错误。
更新
报告为问题 1194:JsonTextReader.ParseNumber 在 ThrowReaderError 后导致错误,并被 Newtonsoft 关闭,因为在当时的版本中无法重现,该版本随后作为Json.NET 10.0.1发布。
原答案
这可能是 中的一个错误JsonTextReader。
其中JsonTextReader.ParseNumber(ReadType readType, char firstChar, int initialPosition)有以下稍微简化的逻辑:
else if (readType == ReadType.ReadAsInt32)
{
// Snip
int value;
ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out value);
if (parseResult == ParseResult.Success)
{
numberValue = value;
}
else if (parseResult == ParseResult.Overflow)
{
throw ThrowReaderError("JSON integer {0} is too large or small for an Int32.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
else
{
throw ThrowReaderError("Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
}
numberType = JsonToken.Integer;
}
// Snip
// Finally, after successfully parsing the number
ClearRecentString();
// index has already been updated
SetToken(numberType, numberValue, false);
Run Code Online (Sandbox Code Playgroud)
当 引发异常时ThrowReadError(),流位置已经超过了太大的整数。但是, 的值JsonReader.TokenType尚未更新,仍然返回JsonToken.PropertyName成功解析的最后一个标记,即"myint"名称。后来,异常被吞掉并忽略后,流位置和当前令牌值不一致导致"Mybool"属性名称被跳过,从而导致第二个错误。
如果在调试器中抛出异常时我手动调用
SetToken(JsonToken.Undefined);
ClearRecentString();
Run Code Online (Sandbox Code Playgroud)
然后可以成功解析文件的其余部分。(我不确定JsonToken.Undefined这里的选择是否正确。)
您可能想向 Newtonsoft报告问题。
由于JsonReader未传递到错误处理程序,我能找到的唯一解决方法是按JsonTextReader如下方式进行子类化:
public class FixedJsonTextReader : JsonTextReader
{
public FixedJsonTextReader(TextReader reader) : base(reader) { }
public override int? ReadAsInt32()
{
try
{
return base.ReadAsInt32();
}
catch (JsonReaderException)
{
if (TokenType == JsonToken.PropertyName)
SetToken(JsonToken.None);
throw;
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后执行以下操作:
var errors = new List<string>();
var json2 = "{\"myint\":3554860000,\"Mybool\":false}";
using (var reader = new FixedJsonTextReader(new StringReader(json2)))
{
var settings = new JsonSerializerSettings
{
Error = delegate(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
{
Debug.WriteLine(args.ErrorContext.Error.Message);
errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
}
};
var i = JsonSerializer.CreateDefault(settings).Deserialize<MyClass>(reader);
}
Assert.IsTrue(errors.Count <= 1); // Passes
Run Code Online (Sandbox Code Playgroud)