webentwicklung-frage-antwort-db.com.de

Deserialize json mit bekannten und unbekannten Feldern

Folgendes Json-Ergebnis gegeben: Das Standard-Json-Ergebnis hat eine bekannte Menge von Feldern:

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

Kann jedoch mit zusätzlichen Feldern (in diesem Beispiel _unknown_field_name_1 und _unknown_field_name_2) erweitert werden, deren Namen bei der Abfrage des Ergebnisses nicht bekannt sind.

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

Ich möchte, dass das json-Ergebnis in eine Klasse mit Eigenschaften für die bekannten Felder serialisiert und deserialisiert wird und die unbekannten Felder (für die keine Eigenschaften vorhanden sind) einer Eigenschaft (oder mehreren Eigenschaften) wie einem Wörterbuch zuordnen abgerufen und geändert.

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

Ich denke, ich brauche eine Möglichkeit, einen Json-Serializer anzuschließen und die Zuordnung für die fehlenden Mitglieder selbst vorzunehmen (sowohl für die Serialisierung als auch für die Deserialisierung). Ich habe verschiedene Möglichkeiten in Betracht gezogen:

  • json.net und benutzerdefinierte Vertragslöser (kann nicht herausfinden, wie es geht)
  • datacontract Serializer (kann nur onserialized, onserializing überschreiben)
  • serialisierung in dynamisches und benutzerdefiniertes Mapping (dies kann funktionieren, scheint jedoch eine Menge Arbeit zu sein)
  • produkt von DynamicObject erben lassen (Serialisierer funktionieren mit Reflektion und rufen die trygetmember- und trysetmember-Methoden nicht auf)

Ich verwende restsharp, aber jeder Serializer kann angeschlossen werden.

Oh, und ich kann das Json-Ergebnis nicht ändern, und dies oder dies hat mir auch nicht geholfen.

Update: Dies sieht eher so aus: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx

45
nickvane

Eine noch einfachere Möglichkeit, dieses Problem zu lösen, wäre die Verwendung von JsonExtensionDataAttribute von JSON .NET

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

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

Es gibt ein Beispiel davon im Projektblog hier

UPDATE Bitte beachten Sie, dass dies JSON .NET Version 5 und höher erfordert

58
cecilphillip

Siehe https://Gist.github.com/LodewijkSioen/5101814

Was Sie gesucht haben, war eine benutzerdefinierte JsonConverter

10
Lodewijk

Auf diese Weise könnten Sie es lösen, auch wenn ich es nicht so mag. Ich habe es mit Newton/JSON.Net gelöst. Ich denke, Sie könnten den JsonConverter auch zur Deserialisierung verwenden. 

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

}

4
Jordy Langen

Ich habe mir ein ähnliches Thema angesehen und diesen Beitrag gefunden.

Hier ist eine Möglichkeit, die Reflektion zu verwenden.

Um es allgemeiner zu machen, sollte der Typ der Eigenschaft überprüft werden, anstatt ToString () in propertyInfo.SetValue zu verwenden, es sei denn, bei OFC sind alle tatsächlichen Eigenschaften Strings.

Eigenschaftennamen in Kleinbuchstaben sind in C # nicht standardmäßig, aber da GetProperty die Groß- und Kleinschreibung berücksichtigt, gibt es einige andere Optionen.

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);
}
0
pasx

Ich dachte, ich würde meinen Hut in den Ring werfen, da ich kürzlich ein ähnliches Problem hatte. Hier ist ein Beispiel für die JSON, die ich deserialisieren wollte:

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

Dies ist für ein System, bei dem Überschreibungen auf verschiedenen Ebenen gelten und im Baum vererbt werden. In jedem Fall war das Datenmodell, das ich wollte, etwas, das es mir erlaubte, eine Eigentumstasche mit diesen speziellen Erbschaftsregeln zur Verfügung zu haben.

Was ich am Ende bekam, war folgendes:

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

Die Grundidee lautet:

  1. Das PropertyBagModel bei der Deserialisierung durch JSON.NET enthält die Felder ByPlan, ByClassAndTier usw. und das private Feld _extensionData.
  2. Dann ruft JSON.NET die private OnDeserialized () -Methode auf, und die Daten werden nach Bedarf von _extensionData nach Values ​​verschoben (oder sonst auf dem Boden abgelegt - vermutlich könnten Sie dies protokollieren, wenn Sie etwas wissen wollten). Wir entfernen dann den zusätzlichen Speicher aus _extensionData, damit kein Speicherplatz verbraucht wird.
  3. Bei der Serialisierung ruft die OnSerializing-Methode Aufrufe ab, bei denen Elemente in _extensionData verschoben werden, damit sie gespeichert werden.
  4. Wenn die Serialisierung abgeschlossen ist, wird OnSerialized aufgerufen und wir entfernen das zusätzliche Material aus _extensionData.

Wir könnten das _extensionData-Wörterbuch bei Bedarf weiter löschen und neu erstellen, aber ich habe keinen echten Wert darin gesehen, da ich nicht jede Menge dieser Objekte verwende. Dazu erstellen wir einfach OnSerializing und löschen OnSerialized. Bei der OnDeserialisierung können wir sie statt löschen.

0
Kelly L