2016-05-06 6 views
6

私はこの簡略化された例に似たJSON入力を持っています。基づいてできるだけ厳密に型付けされたメンバーを持つExpandoObject(またはDictionary)に逆シリアル化することはできますか?

{ 
    "model1": { 
    "$type": "MyType, MyAssembly", 
    "A": 5 
    }, 
    "model2": { 
    "C": "something" 
} 

私は何を達成したいのですが、私はトップレベルExpandoObjectを意味する「ハイブリッド」の結果、ある2つのプロパティmodel1model2を有するがmodel1MyTypeの強いタイプを持っているでしょう( model2はタイプ情報を持たないので、ネストされたExpandoObjectです。このロジックは、ネストレベルが深いほど同じである必要があります(私の更新を参照)。この例はこの点で単純化されています。

私の問題は、「ハイブリッド性」を達成できないことです.1つの方法で、完全に厳密に型指定された結果を得ることができます(トップレベルのオブジェクトが強く型付けされている場合)、私は完全に動的な結果を得ることができます(ExpandoObject、またはこのシナリオでは意味がないJObjectを持つことができる3番目の方法です)。

// this will give a fully dynamic result, regardless the child type information 
var result = JsonConvert.DeserializeObject<ExpandoObject>(input, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }); 

UPDATEは

私は、一般的なIDictionaryにデシリアライズを試してきた、と私は強く得ることができる方法は、技術的に私の例を解決し、トップレベルの子のプロパティ、の結果を入力しました。しかし、それより低いレベルではまだ動作していないので、型なしの子プロパティの結果はJObjectとなります。だから全体的に私の実際のユースケースのための良い解決策ではありません。ここで

答えて

3

問題がJson.NETのExpandoObjectConverterは単純な"$type""id"または"$ref"など、独自のメタデータプロパティのいずれかを処理しないということです。 Json.NETは、オープンソースとそのMITライセンスallows modificationあるので、

しかし、最も簡単な解決策はExpandoObjectConverterのあなた自身のコピーを作成し、Json.NET Deserialization into dynamic object with referencingの線に沿って、あなたのニーズに適合させることであり得ます。あなたにもいくつかの低レベルのJSONユーティリティをコピーする必要があります:

/// <summary> 
/// Converts an ExpandoObject to and from JSON. 
/// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/ExpandoObjectConverter.cs 
/// License: https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md 
/// </summary> 
public class TypeNameHandlingExpandoObjectConverter : JsonConverter 
{ 
    /// <summary> 
    /// Writes the JSON representation of the object. 
    /// </summary> 
    /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param> 
    /// <param name="value">The value.</param> 
    /// <param name="serializer">The calling serializer.</param> 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     // can write is set to false 
    } 

    /// <summary> 
    /// Reads the JSON representation of the object. 
    /// </summary> 
    /// <param name="reader">The <see cref="JsonReader"/> to read from.</param> 
    /// <param name="objectType">Type of the object.</param> 
    /// <param name="existingValue">The existing value of object being read.</param> 
    /// <param name="serializer">The calling serializer.</param> 
    /// <returns>The object value.</returns> 
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     return ReadValue(reader, serializer); 
    } 

    private object ReadValue(JsonReader reader, JsonSerializer serializer) 
    { 
     if (!reader.MoveToContent()) 
     { 
      throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject."); 
     } 

     switch (reader.TokenType) 
     { 
      case JsonToken.StartObject: 
       return ReadObject(reader, serializer); 
      case JsonToken.StartArray: 
       return ReadList(reader, serializer); 
      default: 
       if (JsonTokenUtils.IsPrimitiveToken(reader.TokenType)) 
       { 
        return reader.Value; 
       } 

       throw JsonSerializationExceptionHelper.Create(reader, string.Format("Unexpected token when converting ExpandoObject: {0}", reader.TokenType)); 
     } 
    } 

    private object ReadList(JsonReader reader, JsonSerializer serializer) 
    { 
     IList<object> list = new List<object>(); 

     while (reader.Read()) 
     { 
      switch (reader.TokenType) 
      { 
       case JsonToken.Comment: 
        break; 
       default: 
        object v = ReadValue(reader, serializer); 

        list.Add(v); 
        break; 
       case JsonToken.EndArray: 
        return list; 
      } 
     } 

     throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject."); 
    } 

    private object ReadObject(JsonReader reader, JsonSerializer serializer) 
    { 
     if (serializer.TypeNameHandling != TypeNameHandling.None) 
     { 
      var obj = JObject.Load(reader); 

      Type polymorphicType = null; 
      var polymorphicTypeString = (string)obj["$type"]; 
      if (polymorphicTypeString != null) 
      { 
       if (serializer.TypeNameHandling != TypeNameHandling.None) 
       { 
        string typeName, assemblyName; 
        ReflectionUtils.SplitFullyQualifiedTypeName(polymorphicTypeString, out typeName, out assemblyName); 
        polymorphicType = serializer.Binder.BindToType(assemblyName, typeName); 
       } 
       obj.Remove("$type"); 
      } 

      if (polymorphicType == null || polymorphicType == typeof(ExpandoObject)) 
      { 
       using (var subReader = obj.CreateReader()) 
        return ReadExpandoObject(subReader, serializer); 
      } 
      else 
      { 
       using (var subReader = obj.CreateReader()) 
        return serializer.Deserialize(subReader, polymorphicType); 
      } 
     } 
     else 
     { 
      return ReadExpandoObject(reader, serializer); 
     } 
    } 

    private object ReadExpandoObject(JsonReader reader, JsonSerializer serializer) 
    { 
     IDictionary<string, object> expandoObject = new ExpandoObject(); 

     while (reader.Read()) 
     { 
      switch (reader.TokenType) 
      { 
       case JsonToken.PropertyName: 
        string propertyName = reader.Value.ToString(); 

        if (!reader.Read()) 
        { 
         throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject."); 
        } 

        object v = ReadValue(reader, serializer); 

        expandoObject[propertyName] = v; 
        break; 
       case JsonToken.Comment: 
        break; 
       case JsonToken.EndObject: 
        return expandoObject; 
      } 
     } 

     throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject."); 
    } 

    /// <summary> 
    /// Determines whether this instance can convert the specified object type. 
    /// </summary> 
    /// <param name="objectType">Type of the object.</param> 
    /// <returns> 
    ///  <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>. 
    /// </returns> 
    public override bool CanConvert(Type objectType) 
    { 
     return (objectType == typeof(ExpandoObject)); 
    } 

    /// <summary> 
    /// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON. 
    /// </summary> 
    /// <value> 
    ///  <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>. 
    /// </value> 
    public override bool CanWrite 
    { 
     get { return false; } 
    } 
} 

internal 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; 
     } 
    } 
} 

internal static class JsonReaderExtensions 
{ 
    // Adapted from internal bool JsonReader.MoveToContent() 
    // https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonReader.cs#L1145 
    public static bool MoveToContent(this JsonReader reader) 
    { 
     if (reader == null) 
      throw new ArgumentNullException(); 
     JsonToken t = reader.TokenType; 
     while (t == JsonToken.None || t == JsonToken.Comment) 
     { 
      if (!reader.Read()) 
      { 
       return false; 
      } 

      t = reader.TokenType; 
     } 

     return true; 
    } 
} 

internal static class JsonSerializationExceptionHelper 
{ 
    public static JsonSerializationException Create(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); 
    } 
} 

internal static class ReflectionUtils 
{ 
    // Utilities taken from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs 
    // I couldn't find a way to access these directly. 

    public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName) 
    { 
     int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName); 

     if (assemblyDelimiterIndex != null) 
     { 
      typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim(); 
      assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim(); 
     } 
     else 
     { 
      typeName = fullyQualifiedTypeName; 
      assemblyName = null; 
     } 
    } 

    private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName) 
    { 
     int scope = 0; 
     for (int i = 0; i < fullyQualifiedTypeName.Length; i++) 
     { 
      char current = fullyQualifiedTypeName[i]; 
      switch (current) 
      { 
       case '[': 
        scope++; 
        break; 
       case ']': 
        scope--; 
        break; 
       case ',': 
        if (scope == 0) 
        { 
         return i; 
        } 
        break; 
      } 
     } 

     return null; 
    } 
} 

そして、それが好きで使用します。

var settings = new JsonSerializerSettings 
{ 
    Formatting = Newtonsoft.Json.Formatting.Indented, 
    TypeNameHandling = TypeNameHandling.Auto, 
    Converters = new [] { new TypeNameHandlingExpandoObjectConverter() }, 
}; 

var expando2 = JsonConvert.DeserializeObject<ExpandoObject>(input, settings); 

プロトタイプfiddle

+0

詳細な回答ありがとうございました。私はそのような解決策を事前に考えていました。それはうまく動作しますが、私はJson.NETのコンテキスト外で問題を解決することができました(ASP.NET MVCモデルのバインディングにはこの「機能」が必要でしたが、一般的にはカスタムモデルバインダーで実装しています)。 –

0

は、私はそれを行うだろう方法は次のとおりです。

void Main() 
{ 
    var json = "{\r\n \"model1\": {\r\n \"$type\": \"MyType, MyAssembly\",\r\n \"A\": 5\r\n },\r\n \"model2" + 
     "\": {\r\n \"C\": \"something\"\r\n}}"; 
    var result = JsonConvert.DeserializeObject<Result>(json); 
} 

public class Result 
{ 
    public MyType Model1 { get; set; } 
    public ExpandoObject Model2 { get; set;} 
} 
public class MyType { public int A { get; set;} } 

また、(あなたがresult.Model2.somethingのような構文を使用して、そのプロパティにアクセスすることができます)dynamicの種類をResult.Model2を与えることができ、またはJSON.NETのJObjectは、より多くのですJSON指向です。しかし

、あなたがTypeNameHandling settingを使用することができ、あなたがResultのようなクラスを望んでいないことを言っているが、あなたはJSONの$typeは、特定のインスタンスタイプを決定することができるようにしたい場合。

var result = JsonConvert.DeserializeObject<ExpandoObject>(
    json, 
    new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}); 

クライアント提供のJSON値で.NET環境で任意の型をインスタンス化できるようにする場合は、セキュリティ上の意味があることに注意してください。

関連する問題