144 votes

Comment gérer à la fois un seul élément et un tableau pour la même propriété en utilisant JSON.net

Je suis en train de corriger ma bibliothèque SendGridPlus pour gérer les événements SendGrid, mais j'ai quelques problèmes avec le traitement incohérent des catégories dans l'API.

Dans l'exemple de charge utile suivant tiré de la référence de l'API SendGrid, vous remarquerez que la propriété category pour chaque élément peut être soit une chaîne unique, soit un tableau de chaînes.

[
  {
    "email": "john.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  },
  {
    "email": "jane.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  }
]

Il semble que mes options pour faire fonctionner JSON.NET de cette manière sont de corriger la chaîne avant son traitement, ou de configurer JSON.NET pour accepter les données incorrectes. Je préférerais éviter tout traitement de chaîne si possible.

Y a-t-il un autre moyen de gérer cela en utilisant Json.Net ?

0voto

Tim Gabrhel Points 270

Vous pouvez utiliser un JSONConverterAttribute disponible ici : http://james.newtonking.com/projects/json/help/

En supposant que vous avez une classe qui ressemble à ceci

public class RootObject
{
    public string email { get; set; }
    public int timestamp { get; set; }
    public string smtpid { get; set; }
    public string @event { get; set; }
    public string category[] { get; set; }
}

Vous décorez la propriété category comme vu ici:

    [JsonConverter(typeof(SendGridCategoryConverter))]
    public string category { get; set; }

public class SendGridCategoryConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return true; // ajoutez votre propre logique
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
   // travaillez ici pour gérer le retour du tableau quel que soit le nombre d'objets
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Laissé en exercice pour le lecteur :)
    throw new NotImplementedException();
  }
}

0voto

Andre Fritzsche Points 116

J'avais un problème très similaire. Ma demande Json m'était complètement inconnue. Je savais seulement.

Il y aura un identifiant d'objet dedans et quelques paires clés-valeurs ET des tableaux anonymes.

Je l'ai utilisé pour un modèle EAV que j'ai créé:

Ma demande JSON:

{"objectId": 2, "firstName": "Hans", "email" :[ "a@b.de","a@c.de"], "name": "Andre", "something" :["232","123"] }

Ma classe que j'ai définie:

[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject
{
    public AnonymObject()
    {
        fields = new Dictionary();
        list = new List();
    }

    public string objectid { get; set; }
    public Dictionary fields { get; set; }
    public List list { get; set; }
}

et maintenant que je veux désérialiser des attributs inconnus avec leur valeur et des tableaux à l'intérieur, mon convertisseur ressemble à ceci:

   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
        bool isList = false;
        StringBuilder listValues = new StringBuilder();

        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.EndObject) continue;

            if (isList)
            {
                while (reader.TokenType != JsonToken.EndArray)
                {
                    listValues.Append(reader.Value.ToString() + ", ");

                    reader.Read();
                }
                anonym.list.Add(listValues.ToString());
                isList = false;

                continue;
            }

            var value = reader.Value.ToString();

            switch (value.ToLower())
            {
                case "objectid":
                    anonym.objectid = reader.ReadAsString();
                    break;
                default:
                    string val;

                    reader.Read();
                    if(reader.TokenType == JsonToken.StartArray)
                    {
                        isList = true;
                        val = "ValueDummyForEAV";
                    }
                    else
                    {
                        val = reader.Value.ToString();
                    }
                    try
                    {
                        anonym.fields.Add(value, val);
                    }
                    catch(ArgumentException e)
                    {
                        throw new ArgumentException("Multiple Attribute found");
                    }
                    break;
            }

        }

        return anonym;
    }

Donc maintenant à chaque fois que j'obtiens un AnonymObject, je peux itérer à travers le dictionnaire et à chaque fois qu'il y a mon indicateur "ValueDummyForEAV", je passe à la liste, je lis la première ligne et je divise les valeurs. Ensuite, je supprime la première entrée de la liste et je continue l'itération à partir du dictionnaire.

Peut-être que quelqu'un a le même problème et peut utiliser ceci :)

Cordialement Andre

0voto

Serge Points 7425

Vous n'avez pas besoin de convertisseurs de json personnalisés.

vous pouvez créer une liste d'éléments en utilisant un simple helper

List items = JArray.Parse(json).Select(i => GetItem(i)).ToList();

public Item GetItem(JToken item)
{
    if (((JObject)item)["category"].Type != JTokenType.Array)
        item["category"] = new JArray(item["category"]);

    return item.ToObject();
}

ou vous pouvez créer un JsonConstructor très simple

List items = JsonConvert.DeserializeObject>(json);

public partial class Item
{
    // ... toutes les autres propriétés

   public List Category { get; set; }

    [JsonConstructor]
    public Item(JToken category)
    {
        if (category.GetType().Name == "JArray")
            Category = category.ToObject>();
        else
            Category = new List { category.ToString() };
    }
    public Item() { }
}

ou vous pouvez utiliser un convertisseur json

using Newtonsoft.Json;

public partial class Item
{
    // ... toutes les autres propriétés
    [Newtonsoft.Json.JsonConverter(typeof(StringToListConverter))]
    [JsonPropertyName("category")]
    public List Category { get; set; }
}

public class StringToListConverter : Newtonsoft.Json.JsonConverter>
{
    public override List ReadJson(JsonReader reader, Type objectType, List existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        var jt = JToken.Load(reader);
        return jt.Type == JTokenType.Array 
                ? jt.ToObject>()
                : new List { (string)jt };
    }
    public override void WriteJson(JsonWriter writer, List value, Newtonsoft.Json.JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
    public override bool CanWrite
    {
        get { return false; }
    }
}

et comme je n'ai pas trouvé de solution si System.Text.Json est utilisé

using System.Text.Json;

List items = System.Text.Json.JsonSerializer.Deserialize>(json);

public partial class Item
{
    // ... toutes les autres propriétés
    [System.Text.Json.Serialization.JsonConverter(typeof(StringToListConverter))]
    [JsonPropertyName("category")]
    public List Category { get; set; }
}

public class StringToListConverter : System.Text.Json.Serialization.JsonConverter>
{
    public override List Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var jsonDoc = JsonDocument.ParseValue(ref reader);

        var element = jsonDoc.RootElement;
        return element.ValueKind == JsonValueKind.String
            ? new List { element.GetString() }
            : System.Text.Json.JsonSerializer.Deserialize>(element.GetRawText());
    }
    public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

-3voto

J'ai trouvé une autre solution qui peut gérer la catégorie en tant que chaîne ou tableau en utilisant un objet. De cette façon, je n'ai pas besoin de manipuler le sérialiseur JSON.

Jetez-y un coup d'œil si vous avez le temps et dites-moi ce que vous en pensez. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook

Elle est basée sur la solution à https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ mais j'ai également ajouté la conversion de date à partir du timestamp, mis à jour les variables pour refléter le modèle SendGrid actuel (et fait fonctionner les catégories).

J'ai également créé un gestionnaire avec l'option d'authentification de base. Consultez les fichiers ashx et les exemples.

Merci!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X