复用JsonSerializerOptions遇只读/已使用异常,如何检测?
我尝试在多处复用JsonSerializerOptions,编写了如下代码:
public static void ConfigureJsonSerializerOptions(JsonSerializerOptions jsonSerializerOptions) { jsonSerializerOptions.PropertyNameCaseInsensitive = true; jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; jsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; jsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); jsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; jsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; jsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; jsonSerializerOptions.AllowTrailingCommas = true; jsonSerializerOptions.WriteIndented = true; } private static JsonSerializerOptions options; public static JsonSerializerOptions Options { get { if (options == null) { options = new JsonSerializerOptions(); ConfigureJsonSerializerOptions(options); options.Converters.Add(new JsonStringEnumConverter()); } return options; } }
但运行时抛出异常:
An exception of type 'System.InvalidOperationException' occurred in System.Text.Json.dll but was not handled in user code: 'This JsonSerializerOptions instance is read-only or has already been used in serialization or deserialization.'
我想知道如何检测该状态,因为JsonSerializerOptions实例并没有IsReadOnly属性。
问题原因
JsonSerializerOptions实例在第一次用于序列化或反序列化操作后,会被框架内部标记为只读,此时再修改它的属性或添加转换器就会抛出上述异常。官方并未提供公开的IsReadOnly属性来直接检测这个状态,但可以通过以下几种方式实现检测:
1. 捕获异常检测
通过尝试修改一个不影响业务的属性(比如临时切换WriteIndented的值),如果抛出InvalidOperationException,则说明实例已处于只读状态:
public static bool IsJsonOptionsReadOnly(JsonSerializerOptions options) { try { // 临时修改属性再还原,测试是否可写 var originalValue = options.WriteIndented; options.WriteIndented = !originalValue; options.WriteIndented = originalValue; return false; } catch (InvalidOperationException) { return true; } }
2. 自行维护状态标记
在代码中手动记录JsonSerializerOptions是否已被使用。比如在第一次调用序列化/反序列化的地方标记一个布尔变量,后续修改前先检查这个标记:
private static JsonSerializerOptions _options; private static bool _optionsHasBeenUsed; public static JsonSerializerOptions Options { get { if (_options == null) { _options = new JsonSerializerOptions(); ConfigureJsonSerializerOptions(_options); _options.Converters.Add(new JsonStringEnumConverter()); } return _options; } } // 调用序列化/反序列化时标记状态 public static T Deserialize<T>(string json) { _optionsHasBeenUsed = true; return JsonSerializer.Deserialize<T>(json, Options); } // 检测方法 public static bool IsOptionsReadOnly() => _optionsHasBeenUsed;
3. 反射读取内部字段(不推荐)
通过反射读取JsonSerializerOptions内部的_isReadOnly字段,但这种方式依赖框架内部实现,可能随.NET版本更新而失效,不建议在生产环境使用:
using System.Reflection; public static bool IsJsonOptionsReadOnly(JsonSerializerOptions options) { var readOnlyField = typeof(JsonSerializerOptions) .GetField("_isReadOnly", BindingFlags.Instance | BindingFlags.NonPublic); return readOnlyField != null && (bool)readOnlyField.GetValue(options); }
优化建议
为了彻底避免这类异常,建议确保JsonSerializerOptions在初始化完成后不再被修改。可以使用Lazy<T>实现线程安全的懒加载,确保配置只初始化一次:
public static void ConfigureJsonSerializerOptions(JsonSerializerOptions jsonSerializerOptions) { jsonSerializerOptions.PropertyNameCaseInsensitive = true; jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; jsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; jsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All); jsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; jsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; jsonSerializerOptions.UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode; jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; jsonSerializerOptions.AllowTrailingCommas = true; jsonSerializerOptions.WriteIndented = true; jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); } private static readonly Lazy<JsonSerializerOptions> _lazyOptions = new Lazy<JsonSerializerOptions>(() => { var options = new JsonSerializerOptions(); ConfigureJsonSerializerOptions(options); return options; }); public static JsonSerializerOptions Options => _lazyOptions.Value;
内容的提问来源于stack exchange,提问作者Big boy

