2013-03-06 9 views
37

Given następujący wynik json: Wynik domyślny json ma znany zestaw pól:Cofnięcie json ze znanymi i nieznanymi pól

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

Ale może być rozszerzony o dodatkowe pola (w tym przykładzie _unknown_field_name_1 i _unknown_field_name_2) z których nazwy nie są znane podczas żądania wyniku.

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

Chciałbym wynik json być szeregowane i rozszeregować do iz klasą z właściwości dla znanych polach i map nieznanych pól (dla których nie ma żadnych właściwości) do nieruchomości (lub wielu właściwości) jak słownik, aby można było uzyskać do nich dostęp i zmodyfikować.

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

Chyba muszę drogę do podłączenia do serializatora json i zrobić mapowanie dla brakujących członków siebie (zarówno dla serialize i deserializacji). czekałem na różnych możliwości:

  • json.net i niestandardowe zamówienia rozpoznawania nazw (nie może dowiedzieć się, jak to zrobić)
  • DataContract serializatora (może tylko przesłonić onserialized, onserializing)
  • serialize do dynamicznego i zrobić niestandardowego mapowania (może to działać, ale wydaje się to dużo pracy)
  • dopuścić dziedziczenie z DynamicObject (serializers pracować z refleksji, a nie powoływać się na metody trygetmember i trysetmember)

Używam restsharp, ale każdy serializer może być podłączony.

Aha, i nie mogę zmienić wynik json i this lub this mi nie pomogło.

Aktualizacja: To wygląda bardziej jak to: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx

+0

nie można czerpać z 'Dictionary ' –

+0

Wynik json posiada również tablice, które mapują do listy właściwości. Json w pytaniu jest zredukowaną częścią bardziej złożonego wyniku json. Więc nie. – nickvane

Odpowiedz

44

Jeszcze łatwiejsza możliwość rozwiązania tego problemu byłoby użyć JsonExtensionDataAttribute z JSON .NET

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

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

Jest próbka tego na blogu projektu here

UPDATE Należy pamiętać, że wymaga to JSON .NET v5 w wersji 5 i powyżej

+0

Musiałbym to wypróbować, ale wygląda na to, że potrzebuję rozwiązania. Uwaga: jest to dostępne tylko w json.net 5 lub nowszej wersji. – nickvane

+0

Tak, masz prawo v5 wydanie 5 – cecilphillip

+0

Czy jest jakiś sposób na pominięcie niektórych wartości w JToken? Mam problem, w którym dodaje parametr id jako extraField, co powoduje, że wynikowy json jest nieprawidłowy, ponieważ już mam parametr id. – Zaphod

10
+0

Chciałem uczynić twoje rozwiązanie bardziej ogólnym i użyć jak najwięcej logiki json.net, ale potrzebuję tak dużej kontroli nad wyjściem jsona, że ​​będę musiał to zrobić tak, jak proponowałeś. Dziękuję za nieskończoną mądrość :) – nickvane

+1

Pierwszy link to 404-ing. Rozważ dołączenie reprezentatywnej próbki następnym razem, jestem pewna, że ​​na drugim końcu tego linku znajdowała się cenna treść! –

+0

To jest poprawny adres URL: https://gist.github.com/LodewijkSioen/5101814 – nickvane

4

To sposób można go rozwiązać, chociaż nie lubię tego tak bardzo. Rozwiązałem go za pomocą Newtona/JSON.Net. Przypuszczam, że możesz użyć JsonConverter również do deserializacji.

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

Myślałem, że rzucę kapelusz na ringu, ponieważ ostatnio miałem podobny problem.Oto przykład z JSON chciałem deserializowania:

{ 
    "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" 
        } 
       } 
      } 
     } 
    } 
} 

To jest dla systemu, w którym zastępuje zastosowanie na różnych poziomach i są dziedziczone w dół drzewa. W każdym razie model danych, który chciałem, był czymś, co pozwoliłoby mi na posiadanie torby z własnymi zasadami dostarczania również tych specjalnych dziedziczenia.

Co skończyło się był następujący:

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(); 
    } 
} 

Podstawowa idea jest taka:

  1. PropertyBagModel na deserializacji przez Json.NET ma ByPlan, ByClassAndTier itd pola zaludnionych a także zawiera prywatne pole _extensionData.
  2. Następnie JSON.NET wywołuje prywatną metodę OnDeserialized(), która przeniesie dane z _extensionData do wartości (lub upuść go na podłodze w innym przypadku - prawdopodobnie mógłbyś to zarejestrować, gdyby było to coś, co chciałeś wiedzieć). Następnie usuwamy dodatkową porcję z _extensionData, aby nie zużywała pamięci.
  3. W przypadku serializacji, metoda OnSerializing otrzymuje wywołania, w których przenosimy elementy do pliku _extensionData, aby je zapisać.
  4. Po zakończeniu serializacji wywoływana jest funkcja OnSerialized, a my usuwamy dodatkowe pliki z pliku _extensionData.

Możemy dalej usuwać i odtwarzać słownik _extensionData w razie potrzeby, ale nie widzę prawdziwej wartości w tym, ponieważ nie używam ton tych obiektów. Aby to zrobić, wystarczy utworzyć na OnSerializing i usunąć na OnSerialized. Na OnDeserializing, zamiast czyszczenia, możemy go uwolnić.

0

Szukałem podobnego problemu i znalazłem ten wpis.

Oto sposób, aby to zrobić za pomocą refleksji.

Aby było bardziej ogólne, należy sprawdzić typ właściwości, zamiast po prostu używać ToString() w propertyInfo.SetValue, chyba że OFC wszystkie rzeczywiste właściwości są ciągami.

Nazwy właściwości pisane małymi literami nie są standardem w języku C#, ale zważywszy, że w grupie GetProperty rozróżniana jest wielkość liter, istnieje kilka innych opcji.

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); 
} 
Powiązane problemy