Json.NET通过引用反序列化为动态对象

如何让Json.NET反序列化为动态对象,但仍然可以进行参考分辨率?
dynamic d=JsonConvert.DeserializeObject(...)就像
dynamic d=JsonConvert.DeserializeObject(...)返回一个动态对象但它们不解析$ref$id部分。 (例如, ExpandoObject eo只有eo["$ref"]="..." ,并且没有它应该具有的属性,因为它与$id -Object不同

我发现的是我需要合同解析器解析为动态合同 – 只有在我明确告诉Json.NET使用自定义ContractResolverExpandoObject才会这样做。

仍然看起来ExpandoObject是用它自己的Converter解析的,它再次失败。

我尝试过inheritance自IDynamicMetaObjectProvider的自定义类,导致无限循环,看起来不是正确的事情。 我实际上期望一些简单的解决方案让ExpandoObject具有参考分辨率。

有帮助吗?

由于Json.NET是开源的并且其MIT许可允许修改 ,因此最简单的解决方案可能是使其ExpandoObjectConverter适应您的需求:

 ///  /// Converts an ExpandoObject to and from JSON, handling object references. ///  public class ObjectReferenceExpandoObjectConverter : JsonConverter { // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/ExpandoObjectConverter.cs public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // can write is set to false throw new NotImplementedException(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return ReadValue(serializer, reader); } private object ReadValue(JsonSerializer serializer, JsonReader reader) { while (reader.TokenType == JsonToken.Comment) { if (!reader.Read()) throw reader.CreateException("Unexpected end when reading ExpandoObject."); } switch (reader.TokenType) { case JsonToken.StartObject: return ReadObject(serializer, reader); case JsonToken.StartArray: return ReadList(serializer, reader); default: if (JsonTokenUtils.IsPrimitiveToken(reader.TokenType)) return reader.Value; throw reader.CreateException("Unexpected token when converting ExpandoObject"); } } private object ReadList(JsonSerializer serializer, JsonReader reader) { IList list = new List(); while (reader.Read()) { switch (reader.TokenType) { case JsonToken.Comment: break; default: object v = ReadValue(serializer, reader); list.Add(v); break; case JsonToken.EndArray: return list; } } throw reader.CreateException("Unexpected end when reading ExpandoObject."); } private object ReadObject(JsonSerializer serializer, JsonReader reader) { IDictionary expandoObject = null; object referenceObject = null; while (reader.Read()) { switch (reader.TokenType) { case JsonToken.PropertyName: string propertyName = reader.Value.ToString(); if (!reader.Read()) throw new InvalidOperationException("Unexpected end when reading ExpandoObject."); object v = ReadValue(serializer, reader); if (propertyName == "$ref") { var id = (v == null ? null : Convert.ToString(v, CultureInfo.InvariantCulture)); referenceObject = serializer.ReferenceResolver.ResolveReference(serializer, id); } else if (propertyName == "$id") { var id = (v == null ? null : Convert.ToString(v, CultureInfo.InvariantCulture)); serializer.ReferenceResolver.AddReference(serializer, id, (expandoObject ?? (expandoObject = new ExpandoObject()))); } else { (expandoObject ?? (expandoObject = new ExpandoObject()))[propertyName] = v; } break; case JsonToken.Comment: break; case JsonToken.EndObject: if (referenceObject != null && expandoObject != null) throw reader.CreateException("ExpandoObject contained both $ref and real data"); return referenceObject ?? expandoObject; } } throw reader.CreateException("Unexpected end when reading ExpandoObject."); } public override bool CanConvert(Type objectType) { return (objectType == typeof(ExpandoObject)); } public override bool CanWrite { get { return false; } } } public static class JsonTokenUtils { // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/JsonTokenUtils.cs public static bool IsPrimitiveToken(this JsonToken token) { switch (token) { case JsonToken.Integer: case JsonToken.Float: case JsonToken.String: case JsonToken.Boolean: case JsonToken.Undefined: case JsonToken.Null: case JsonToken.Date: case JsonToken.Bytes: return true; default: return false; } } } public static class JsonReaderExtensions { public static JsonSerializationException CreateException(this JsonReader reader, string format, params object[] args) { // Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs var lineInfo = reader as IJsonLineInfo; var path = (reader == null ? null : reader.Path); var message = string.Format(CultureInfo.InvariantCulture, format, args); if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal)) { message = message.Trim(); if (!message.EndsWith(".", StringComparison.Ordinal)) message += "."; message += " "; } message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path); if (lineInfo != null && lineInfo.HasLineInfo()) message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition); message += "."; return new JsonSerializationException(message); } } 

然后使用它像:

  var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, ReferenceLoopHandling = ReferenceLoopHandling.Serialize }; settings.Converters.Add(new ObjectReferenceExpandoObjectConverter()); dynamic d = JsonConvert.DeserializeObject(json, settings); 

我现在的方式是使用后处理步骤和递归函数,它正在执行自己的引用保存和重新布线:

  private static void Reffing(this IDictionary current, Action exchange,IDictionary refdic) { object value; if(current.TryGetValue("$ref", out value)) { if(!refdic.TryGetValue((string) value, out value)) throw new Exception("ref not found "); if (exchange != null) exchange(value); return; } if (current.TryGetValue("$id", out value)) { refdic[(string) value] = current; } foreach (var kvp in current.ToList()) { if (kvp.Key.StartsWith("$")) continue; var expandoObject = kvp.Value as ExpandoObject; if(expandoObject != null) Reffing(expandoObject,o => current[kvp.Key]=o,refdic); var list = kvp.Value as IList; if (list == null) continue; for (var i = 0; i < list.Count; i++) { var lEO = list[i] as ExpandoObject; if(lEO!=null) Reffing(lEO,o => list[i]=o,refdic); } } } 

用作:

  var test = JsonConvert.DeserializeObject(...); var dictionary = new Dictionary(); Reffing(test,null,dictionary);