System.Text.Json反序列化Expr类object属性始终得JsonElement的解决办法
问题描述
我定义了一个表示表达式树的Expr类,包含三个属性:Left(可存储嵌套Expr对象或离散值)、Operator(操作类型枚举)、Right(可存储嵌套Expr对象或离散值),代码如下:
public class Expr { public object Left { get; set; } public OperatorEnum Operator { get; set; } public object Right { get; set; } }
使用System.Text.Json进行反序列化时,无论Left和Right属性存储的是嵌套Expr对象,还是string、decimal等离散值,最终得到的实例中这两个属性始终为JsonElement类型。例如反序列化{ "Left": "foo", "Operator": "Equals", "Right": "bar" }后,expr.Left.GetType()返回的始终是JsonElement。
我已编写了一个JsonConverter类,并将其添加到JsonSerializerOptions中用于序列化和反序列化,但相关经验不足,代码如下:
namespace Test { using System; using System.Collections.Generic; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; public class ExpressionConverter : JsonConverter<Expr> { public override Expr Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { Expr ret = new Expr(); bool inLeft = false; bool inOper = false; bool inRight = false; while (reader.Read()) { if (!inLeft && !inOper && !inRight) { if (reader.TokenType == JsonTokenType.PropertyName) { if (reader.ValueTextEquals("Left")) { inLeft = true; } else if (reader.ValueTextEquals("Operator")) { inOper = true; } else if (reader.ValueTextEquals("Right")) { inRight = true; } } } else { if (inLeft) { if (reader.TokenType == JsonTokenType.String) { string val = reader.GetString(); if (val != null) ret.Left = val.ToString(); else ret.Left = null; } else if (reader.TokenType == JsonTokenType.StartObject) { ret.Left = JsonSerializer.Deserialize<Expr>(ref reader, options); } else if (reader.TokenType == JsonTokenType.StartArray) { ret.Left = JsonSerializer.Deserialize<List<object>>(ref reader, options); } else if (reader.TokenType == JsonTokenType.Number) { ret.Left = reader.GetDecimal(); } else if (reader.TokenType == JsonTokenType.True) { ret.Left = true; } else if (reader.TokenType == JsonTokenType.False) { ret.Left = false; } else { throw new InvalidOperationException("Unexpected token type '" + reader.TokenType.ToString() + "' while processing value for 'Left' expression."); } } else if (inOper) { ret.Operator = (OperatorEnum)(Enum.Parse(typeof(OperatorEnum), reader.GetString())); } else if (inRight) { if (reader.TokenType == JsonTokenType.String) { string val = reader.GetString(); if (val != null) ret.Right = val.ToString(); else ret.Left = null; } else if (reader.TokenType == JsonTokenType.StartObject) { ret.Right = JsonSerializer.Deserialize<Expr>(ref reader, options); } else if (reader.TokenType == JsonTokenType.StartArray) { ret.Right = JsonSerializer.Deserialize<List<object>>(ref reader, options); } else if (reader.TokenType == JsonTokenType.Number) { ret.Right = reader.GetDecimal(); } else if (reader.TokenType == JsonTokenType.True) { ret.Right = true; } else if (reader.TokenType == JsonTokenType.False) { ret.Right = false; } else { throw new InvalidOperationException("Unexpected token type '" + reader.TokenType.ToString() + "' while processing value for 'Left' expression."); } } inLeft = false; inOper = false; inRight = false; } } return ret; } public override void Write(Utf8JsonWriter writer, Expr value, JsonSerializerOptions options) { if (value == null) throw new ArgumentNullException(nameof(value)); writer.WriteStartObject(); if (value.Left is Expr) { writer.WritePropertyName("Left"); JsonSerializer.Serialize(writer, value.Left, options); } else if (IsNumberType(value.Left)) { writer.WriteNumber("Left", Convert.ToDecimal(value.Left)); } else if (IsBooleanType(value.Left)) { writer.WriteBoolean("Left", Convert.ToBoolean(value.Left)); } else { if (value.Left != null) { writer.WriteString("Left", value.Left.ToString()); } else { writer.WriteNull("Left"); } } writer.WriteString("Operator", value.Operator.ToString()); if (value.Right is Expr) { writer.WritePropertyName("Right"); JsonSerializer.Serialize(writer, value.Right, options); } else if (IsNumberType(value.Right)) { writer.WriteNumber("Right", Convert.ToDecimal(value.Right)); } else if (IsBooleanType(value.Right)) { writer.WriteBoolean("Right", Convert.ToBoolean(value.Right)); } else { if (value.Right != null) { writer.WriteString("Right", value.Right.ToString()); } else { writer.WriteNull("Right"); } } writer.WriteEndObject(); } private bool IsNumberType(object value) { if (value == null) return false; return Double.TryParse(value.ToString(), out double _); } private bool IsBooleanType(object value) { if (value == null) return false; return Boolean.TryParse(value.ToString(), out bool _); } } }
请问有没有办法让反序列化后的Left和Right属性正确反映实际值类型(如Expr、string等)?
解决方案
你的转换器思路正确,但存在几个关键问题导致类型转换不符合预期,以下是修正后的完整实现:
核心修正点
- 修复Right属性反序列化bug:原代码处理Right为null时错误赋值给Left,且异常信息错误指向Left。
- 新增Null值处理:原代码未显式处理
JsonTokenType.Null,导致null值无法正确赋值。 - 优化枚举解析:添加大小写不敏感解析逻辑,并处理无效枚举值的异常情况。
- 递归处理数组元素:原数组反序列化
List<object>会导致内部元素仍为JsonElement,需递归解析每个数组元素。 - 改进类型判断逻辑:原
IsNumberType和IsBooleanType通过字符串解析判断类型不准确,改为直接判断对象实际类型。
修正后的转换器代码
namespace Test { using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; public class ExpressionConverter : JsonConverter<Expr> { public override Expr Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("Expected start of object for Expr"); var expr = new Expr(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) return expr; if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("Expected property name"); var propName = reader.GetString(); reader.Read(); // 移动到属性值位置 switch (propName) { case "Left": expr.Left = ReadValue(ref reader, options); break; case "Operator": if (!Enum.TryParse<OperatorEnum>(reader.GetString(), ignoreCase: true, out var op)) throw new JsonException($"Invalid Operator value: {reader.GetString()}"); expr.Operator = op; break; case "Right": expr.Right = ReadValue(ref reader, options); break; default: reader.Skip(); // 跳过未知属性,避免解析失败 break; } } throw new JsonException("Unexpected end of JSON while parsing Expr"); } public override void Write(Utf8JsonWriter writer, Expr value, JsonSerializerOptions options) { if (value == null) throw new ArgumentNullException(nameof(value)); writer.WriteStartObject(); WriteValue(writer, "Left", value.Left, options); writer.WriteString("Operator", value.Operator.ToString()); WriteValue(writer, "Right", value.Right, options); writer.WriteEndObject(); } // 递归读取值,支持嵌套Expr、基础类型、数组 private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) { return reader.TokenType switch { JsonTokenType.StartObject => JsonSerializer.Deserialize<Expr>(ref reader, options), JsonTokenType.StartArray => ReadArray(ref reader, options), JsonTokenType.String => reader.GetString(), JsonTokenType.Number => reader.GetDecimal(), JsonTokenType.True => true, JsonTokenType.False => false, JsonTokenType.Null => null, _ => throw new JsonException($"Unsupported token type: {reader.TokenType}") }; } // 递归解析数组内的每个元素 private List<object> ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options) { var list = new List<object>(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndArray) return list; list.Add(ReadValue(ref reader, options)); } throw new JsonException("Unexpected end of JSON while parsing array"); } // 根据实际类型选择对应的JSON写入方法 private void WriteValue(Utf8JsonWriter writer, string propertyName, object value, JsonSerializerOptions options) { if (value == null) { writer.WriteNull(propertyName); return; } if (!string.IsNullOrEmpty(propertyName)) writer.WritePropertyName(propertyName); switch (value) { case Expr expr: JsonSerializer.Serialize(writer, expr, options); break; case decimal d: writer.WriteNumberValue(d); break; case int i: writer.WriteNumberValue(i); break; case bool b: writer.WriteBooleanValue(b); break; case string s: writer.WriteStringValue(s); break; case IEnumerable<object> array: writer.WriteStartArray(); foreach (var item in array) WriteValue(writer, null, item, options); // 数组元素无需属性名 writer.WriteEndArray(); break; default: writer.WriteStringValue(value.ToString()); break; } } } }
使用示例
确保序列化/反序列化时将转换器添加到JsonSerializerOptions:
var options = new JsonSerializerOptions(); options.Converters.Add(new ExpressionConverter()); // 基础类型反序列化 var basicJson = "{ \"Left\": \"foo\", \"Operator\": \"Equals\", \"Right\": \"bar\" }"; var basicExpr = JsonSerializer.Deserialize<Expr>(basicJson, options); // basicExpr.Left 类型为 string,basicExpr.Right 类型为 string // 嵌套Expr反序列化 var nestedJson = "{ \"Left\": { \"Left\": 123, \"Operator\": \"GreaterThan\", \"Right\": 45 }, \"Operator\": \"And\", \"Right\": true }"; var nestedExpr = JsonSerializer.Deserialize<Expr>(nestedJson, options); // nestedExpr.Left 类型为 Expr,其 Left 为 decimal,Right 为 decimal;nestedExpr.Right 类型为 bool
内容的提问来源于stack exchange,提问作者joelc
相关产品推荐
相关产品推荐

