Étant donné le résultat JSON suivant: Le résultat JSON par défaut comporte un ensemble de champs connu:
{
"id": "7908",
"name": "product name"
}
Mais peut être étendu avec des champs supplémentaires (dans cet exemple, _unknown_field_name_1
et _unknown_field_name_2
) dont les noms ne sont pas connus lors de la demande du résultat.
{
"id": "7908",
"name": "product name",
"_unknown_field_name_1": "some value",
"_unknown_field_name_2": "some value"
}
Je voudrais que le résultat JSON soit sérialisé et désérialisé vers et depuis une classe avec des propriétés pour les champs connus et mappe les champs inconnus (pour lesquels il n'y a pas de propriétés) à une propriété (ou à plusieurs propriétés) comme un dictionnaire afin qu'ils puissent être accédé et modifié.
public class Product
{
public string id { get; set; }
public string name { get; set; }
public Dictionary<string, string> fields { get; set; }
}
Je pense que j'ai besoin d'un moyen de brancher un sérialiseur JSON et de mapper moi-même les membres manquants (à la fois pour la sérialisation et la désérialisation) ... J'ai examiné diverses possibilités:
J'utilise restsharp, mais n'importe quel sérialiseur peut être branché.
Oh, et je ne peux pas changer le résultat JSON, et ceci ou ceci ne m'a pas aidé non plus.
Mise à jour: Cela lui ressemble davantage: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx
Une option encore plus simple pour résoudre ce problème serait d’utiliser le JsonExtensionDataAttribute de JSON .NET
public class MyClass
{
// known field
public decimal TaxRate { get; set; }
// extra fields
[JsonExtensionData]
private IDictionary<string, JToken> _extraStuff;
}
Vous en trouverez un exemple sur le blog du projet ici
UPDATE Veuillez noter que cela nécessite JSON .NET version 5 et ultérieure
Voir https://Gist.github.com/LodewijkSioen/5101814
Ce que vous cherchiez était une coutume JsonConverter
C'est une façon de le résoudre, même si je ne l'aime pas trop. Je l'ai résolu en utilisant Newton/JSON.Net. Je suppose que vous pourriez aussi utiliser le JsonConverter pour la désérialisation.
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; }
}
}
Je cherchais un problème similaire et ai trouvé ce post.
Voici une façon de le faire en utilisant la réflexion.
Pour le rendre plus générique, il convient de vérifier le type de la propriété au lieu d'utiliser simplement ToString () dans propertyInfo.SetValue, sauf si OFC toutes les propriétés réelles sont des chaînes.
De plus, les noms de propriété en minuscule ne sont pas standard en C #, mais étant donné que GetProperty est sensible à la casse, il y a peu d'autres options.
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);
}
Je pensais jeter mon chapeau dans le ring depuis que j'ai eu un problème similaire récemment. Voici un exemple du JSON que je voulais désérialiser:
{
"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"
}
}
}
}
}
}
Cela concerne un système où les substitutions s'appliquent à différents niveaux et sont héritées dans l'arborescence. En tout état de cause, le modèle de données que je souhaitais me permettait de disposer d'un sac de propriété contenant également ces règles d'héritage spéciales.
Ce que j'ai fini par être le suivant:
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();
}
}
L'idée de base est la suivante:
Nous pourrions en outre supprimer et recréer le dictionnaire _extensionData en cas de besoin, mais je n’y voyais aucune valeur réelle, car je n’utilisais pas des tonnes de ces objets. Pour ce faire, nous créerions simplement sur OnSerializing et supprimerions sur OnSerialized. Sur OnDeserializing, au lieu d’effacer, nous pourrions le libérer.