2013-03-06 85 views
37

鑑於以下JSON結果: 默認JSON結果具有已知的字段集:反序列化JSON與已知和未知的領域

{ 
    "id": "7908", 
    "name": "product name" 
} 

但是可以與附加字段來擴展(在此示例_unknown_field_name_1_unknown_field_name_2)請求結果時不知道名稱。

{ 
    "id": "7908", 
    "name": "product name", 
    "_unknown_field_name_1": "some value", 
    "_unknown_field_name_2": "some value" 
} 

我想JSON結果被序列化和反序列化,並從一類具有用於已知字段屬性和映射未知字段(其中沒有屬性)的屬性(或多個屬性)就像字典一樣,可以訪問和修改它們。

public class Product 
{ 
    public string id { get; set; } 
    public string name { get; set; } 
    public Dictionary<string, string> fields { get; set; } 
} 

我想我需要一種方法來插入JSON序列化和失蹤的成員自己(無論是序列化和反序列化)進行映射。 我一直在尋找各種可能性:

  • json.net和定製合同解析器(無法弄清楚如何做到這一點)
  • datacontract串行器(只能覆蓋onserialized,onserializing)
  • 連載動態並進行自定義映射(這可能工作,但似乎很多工作)
  • 讓產物從DynamicObject繼承(串行與反思工作,不調用trygetmember和trysetmember方法)

我使用restsharp,但任何串行器可以插入。

哦,我不能改變JSON結果,並thisthis也幫不了我。

更新: 這看起來更像是它:http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx

+0

你不能從'Dictionary '派生嗎? – 2013-03-06 17:21:22

+0

json結果也有映射到List屬性的數組。問題中的json是更復雜的json結果的縮減部分。所以不行。 – nickvane 2013-03-06 17:25:28

回答

44

一個更簡單的選項,以解決這一問題將是從JSON .NET使用JsonExtensionDataAttribute

public class MyClass 
{ 
    // known field 
    public decimal TaxRate { get; set; } 

    // extra fields 
    [JsonExtensionData] 
    private IDictionary<string, JToken> _extraStuff; 
} 

有這樣的項目博客樣本here

UPDATE請注意,這需要JSON .NET v5 release 5及更高版本

+0

我需要嘗試一下,但看起來像我需要的解決方案。請注意,這隻適用於json.net 5或更高版本。 – nickvane 2014-02-14 15:48:23

+0

是的,你是對的v5發佈5 – cecilphillip 2014-02-14 19:14:58

+0

有沒有什麼辦法可以省略JToken中的一些值?我有一個問題,它添加一個ID參數作爲extraField,這使得生成的JSON無效,因爲我已經有一個ID參數.. – Zaphod 2017-09-13 07:41:46

10

https://gist.github.com/LodewijkSioen/5101814

什麼你要找的是一個自定義JsonConverter

+0

我想讓你的解決方案更通用,儘可能多地使用json.net邏輯,但是我需要對json輸出進行如此多的控制,我必須像你提出的那樣去做。謝謝你的無限智慧:) – nickvane 2013-03-07 19:45:23

+1

第一個鏈接是404-ing。請考慮下次包括一個有代表性的示例,我確信在該鏈接的另一端有有價值的內容! – 2014-02-13 23:03:05

+0

這是正確的網址:https://gist.github.com/LodewijkSioen/5101814 – nickvane 2014-02-15 09:30:40

4

這是你可以解決這個問題的一種方式,但我不喜歡那麼多。我使用Newton/JSON.Net解決了它。我想你也可以使用JsonConverter進行反序列化。

private const string Json = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}"; 

    [TestMethod] 
    public void TestDeserializeUnknownMembers() 
    { 
     var @object = JObject.Parse(Json); 

     var serializer = new Newtonsoft.Json.JsonSerializer(); 
     serializer.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Error; 
     serializer.Error += (sender, eventArgs) => 
      { 
       var contract = eventArgs.CurrentObject as Contract ?? new Contract(); 
       contract.UnknownValues.Add(eventArgs.ErrorContext.Member.ToString(), @object[eventArgs.ErrorContext.Member.ToString()].Value<string>()); 
       eventArgs.ErrorContext.Handled = true; 
      }; 

     using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(Json))) 
     using (StreamReader streamReader = new StreamReader(memoryStream)) 
     using (JsonReader jsonReader = new JsonTextReader(streamReader)) 
     { 
      var result = serializer.Deserialize<Contract>(jsonReader); 
      Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_1")); 
      Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_2")); 
     } 
    } 

    [TestMethod] 
    public void TestSerializeUnknownMembers() 
    { 
     var deserializedObject = new Contract 
     { 
      id = 7908, 
      name = "product name", 
      UnknownValues = new Dictionary<string, string> 
     { 
      {"_unknown_field_name_1", "some value"}, 
      {"_unknown_field_name_2", "some value"} 
     } 
     }; 

     var json = JsonConvert.SerializeObject(deserializedObject, new DictionaryConverter()); 
     Console.WriteLine(Json); 
     Console.WriteLine(json); 
     Assert.AreEqual(Json, json); 
    } 
} 

class DictionaryConverter : JsonConverter 
{ 
    public DictionaryConverter() 
    { 

    } 

    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(Contract); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     var contract = value as Contract; 
     var json = JsonConvert.SerializeObject(value); 
     var dictArray = String.Join(",", contract.UnknownValues.Select(pair => "\"" + pair.Key + "\":\"" + pair.Value + "\"")); 

     json = json.Substring(0, json.Length - 1) + "," + dictArray + "}"; 
     writer.WriteRaw(json); 
    } 
} 

class Contract 
{ 
    public Contract() 
    { 
     UnknownValues = new Dictionary<string, string>(); 
    } 

    public int id { get; set; } 
    public string name { get; set; } 

    [JsonIgnore] 
    public Dictionary<string, string> UnknownValues { get; set; } 
} 

}

0

我以爲我會在環中丟我的帽子,因爲我最近有類似的問題。這是我想反序列化JSON的例子:

{ 
    "agencyId": "agency1", 
    "overrides": { 
     "assumption.discount.rates": "value: 0.07", 
     ".plan": { 
      "plan1": { 
       "assumption.payroll.growth": "value: 0.03", 
       "provision.eeContrib.rate": "value: 0.35" 
      }, 
      "plan2": { 
       ".classAndTier": { 
        "misc:tier1": { 
         "provision.eeContrib.rate": "value: 0.4" 
        }, 
        "misc:tier2": { 
         "provision.eeContrib.rate": "value: 0.375" 
        } 
       } 
      } 
     } 
    } 
} 

這對於覆蓋應用在不同層次和繼承下來的樹的系統。在任何情況下,我想要的數據模型都可以讓我擁有一個帶有這些特殊繼承規則的屬性包。

我最終什麼樣的主意是以下幾點:

public class TestDataModel 
{ 
    public string AgencyId; 
    public int Years; 
    public PropertyBagModel Overrides; 
} 

public class ParticipantFilterModel 
{ 
    public string[] ClassAndTier; 
    public string[] BargainingUnit; 
    public string[] Department; 
} 

public class PropertyBagModel 
{ 
    [JsonExtensionData] 
    private readonly Dictionary<string, JToken> _extensionData = new Dictionary<string, JToken>(); 

    [JsonIgnore] 
    public readonly Dictionary<string, string> Values = new Dictionary<string, string>(); 

    [JsonProperty(".plan", NullValueHandling = NullValueHandling.Ignore)] 
    public Dictionary<string, PropertyBagModel> ByPlan; 

    [JsonProperty(".classAndTier", NullValueHandling = NullValueHandling.Ignore)] 
    public Dictionary<string, PropertyBagModel> ByClassAndTier; 

    [JsonProperty(".bargainingUnit", NullValueHandling = NullValueHandling.Ignore)] 
    public Dictionary<string, PropertyBagModel> ByBarginingUnit; 

    [OnSerializing] 
    private void OnSerializing(StreamingContext context) 
    { 
     foreach (var kvp in Values) 
      _extensionData.Add(kvp.Key, kvp.Value); 
    } 

    [OnSerialized] 
    private void OnSerialized(StreamingContext context) 
    { 
     _extensionData.Clear(); 
    } 

    [OnDeserialized] 
    private void OnDeserialized(StreamingContext context) 
    { 
     Values.Clear(); 
     foreach (var kvp in _extensionData.Where(x => x.Value.Type == JTokenType.String)) 
      Values.Add(kvp.Key, kvp.Value.Value<string>()); 
     _extensionData.Clear(); 
    } 
} 

的基本思路是這樣的:

  1. 通過JSON.NET上反序列化的PropertyBagModel有ByPlan,ByClassAndTier等填充字段並且還具有填充的專用_extensionData字段。
  2. 然後JSON.NET調用私有的OnDeserialized()方法,並將數據從_extensionData移動到適當的值(否則將它放在地板上 - 假設您可以記錄它,如果它是你想知道的東西)。然後我們從_extensionData中移除額外的gunk,所以它不會消​​耗內存。
  3. 在序列化時,OnSerializing方法獲取調用,將東西移動到_extensionData中以便保存。
  4. 序列化完成後,OnSerialized被調用,我們從_extensionData中刪除額外的東西。

我們可以在需要時進一步刪除並重新創建_extensionData字典,但是由於我沒有使用這些對象,所以沒有看到真正的值。爲此,我們只需在OnSerializing上創建並在OnSerialized上刪除。關於反序列化,我們可以釋放它,而不是清除。

0

我正在尋找類似的問題,並找到這個職位。

這是一種使用反射的方法。

爲了使其更通用,應該檢查屬性的類型,而不是僅僅在propertyInfo.SetValue中使用ToString(),除非OFC的所有實際屬性都是字符串。

此外,小寫屬性名稱在C#中不是標準的,但是如果GetProperty區分大小寫,那麼很少有其他選項。

public class Product 
{ 
    private Type _type; 

    public Product() 
    { 
     fields = new Dictionary<string, object>(); 
     _type = GetType(); 
    } 

    public string id { get; set; } 
    public string name { get; set; } 
    public Dictionary<string, object> fields { get; set; } 

    public void SetProperty(string key, object value) 
    { 
     var propertyInfo = _type.GetProperty(key); 
     if (null == propertyInfo) 
     { 
      fields.Add(key,value); 
      return; 
     } 
     propertyInfo.SetValue(this, value.ToString()); 
    } 
} 
... 
private const string JsonTest = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}"; 

var product = new Product(); 
var data = JObject.Parse(JsonTest); 
foreach (var item in data) 
{ 
    product.SetProperty(item.Key, item.Value); 
}