web-dev-qa-db-fra.com

Conversion personnalisée d'objets spécifiques dans JSON.NET

J'utilise JSON.NET pour sérialiser certains de mes objets, et j'aimerais savoir s'il existe un moyen simple de remplacer le convertisseur json.net par défaut uniquement pour un objet spécifique?

Actuellement, j'ai la classe suivante:

public class ChannelContext : IDataContext
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<INewsItem> Items { get; set; }
}

JSON.NET sérialise actuellement les éléments ci-dessus comme:

{
    "Id": 2,
    "Name": "name value",
    "Items": [ item_data_here ]
}

Est-il possible juste pour cette classe spécifique de le formater de cette façon à la place:

"Id_2":
{
    "Name": "name value",
    "Items": [ item data here ]
}

Je suis un peu nouveau sur JSON.NET .. Je me demandais si ce qui précède a quelque chose à voir avec l'écriture d'un convertisseur personnalisé. Je n'ai pas pu trouver d'exemples concrets sur la façon d'écrire un, si quelqu'un peut me signaler une source spécifique, je l'apprécierai vraiment.

J'ai besoin de trouver une solution qui fasse que cette classe spécifique soit toujours la même, car le contexte ci-dessus fait partie d'un contexte encore plus grand que le convertisseur par défaut JSON.NET convertit très bien.

J'espère que ma question est assez claire ...

MISE À JOUR:

J'ai trouvé comment créer un nouveau convertisseur personnalisé (en créant une nouvelle classe qui hérite de JsonConverter et remplace ses méthodes abstraites), j'ai remplacé la méthode WriteJson comme suit:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ChannelContext contextObj = value as ChannelContext;

        writer.WriteStartObject();
        writer.WritePropertyName("id_" + contextObj.Id);
        writer.WriteStartObject();
        writer.WritePropertyName("Name");
        serializer.Serialize(writer, contextObj.Name);

        writer.WritePropertyName("Items");
        serializer.Serialize(writer, contextObj.Items);
        writer.WriteEndObject();
        writer.WriteEndObject();
    }

Cela fait effectivement le travail avec succès, mais ... Je suis intrigué s'il existe un moyen de sérialiser le reste des propriétés de l'objet en réutilisant le JsonSerializer par défaut (ou le convertisseur d'ailleurs) au lieu de "l'écriture" manuelle de l'objet à l'aide du jsonwriter méthodes.

PDATE 2: J'essaie d'obtenir une solution plus générique et j'ai trouvé ce qui suit:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartObject();

        // Write associative array field name
        writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(value));

        // Remove this converter from serializer converters collection
        serializer.Converters.Remove(this);

        // Serialize the object data using the rest of the converters
        serializer.Serialize(writer, value);

        writer.WriteEndObject();
    }

Cela fonctionne très bien lors de l'ajout manuel du convertisseur au sérialiseur, comme ceci:

jsonSerializer.Converters.Add(new AssociativeArraysConverter<DefaultFieldNameResolver>());
jsonSerializer.Serialize(writer, channelContextObj);

Mais ne fonctionne pas lors de l'utilisation de l'attribut [JsonConverter ()] défini sur mon coverter personnalisé au-dessus de la classe ChannelContext en raison d'une boucle d'auto-référence qui se produit lors de l'exécution:

serializer.Serialize(writer, value)

C'est évidemment parce que mon convertisseur personnalisé est maintenant considéré comme le convertisseur par défaut pour la classe une fois qu'il est défini avec JsonConverterAttribute, donc j'obtiens une boucle inifinite. La seule chose à laquelle je peux penser, afin de résoudre ce problème, est d'hériter d'une classe de base jsonconverter et d'appeler la méthode base.serialize () à la place ... Mais une telle classe JsonConverter existe-t-elle même?

Merci beaucoup!

Mikey

31
Mikey S.

Si quelqu'un s'intéresse à ma solution:

Lors de la sérialisation de certaines collections, je voulais créer un tableau json associatif au lieu d'un tableau json standard, afin que mon collègue développeur côté client puisse atteindre ces champs efficacement, en utilisant leur nom (ou clé, d'ailleurs) au lieu de les parcourir.

considérer ce qui suit:

public class ResponseContext
{
    private List<ChannelContext> m_Channels;

    public ResponseContext()
    {
        m_Channels = new List<ChannelContext>();
    }

    public HeaderContext Header { get; set; }

    [JsonConverter(
        typeof(AssociativeArraysConverter<ChannelContextFieldNameResolver>))]
    public List<ChannelContext> Channels
    {
        get { return m_Channels; }
    }

}

[JsonObject(MemberSerialization = MemberSerialization.OptOut)]
public class ChannelContext : IDataContext
{
    [JsonIgnore]
    public int Id { get; set; }

    [JsonIgnore]
    public string NominalId { get; set; }

    public string Name { get; set; }

    public IEnumerable<Item> Items { get; set; }
}

Le contexte de réponse contient l'intégralité de la réponse qui est réécrite au client, comme vous pouvez le voir, il comprend une section appelée "canaux", et au lieu de sortir les contextes des canaux dans un tableau normal, j'aimerais pouvoir sortir dans le manière suivante:

"Channels"
{
"channelNominalId1":
{
  "Name": "name value1"
  "Items": [ item data here ]
},
"channelNominalId2":
{
  "Name": "name value2"
  "Items": [ item data here ]
}
}

Comme je voulais également utiliser ce qui précède pour d'autres contextes, et je pourrais décider d'utiliser une propriété différente comme "clé", ou même choisir de créer mon propre nom unique, qui n'a rien à voir avec aucune propriété, J'avais besoin d'une sorte de solution générique, j'ai donc écrit une classe générique appelée AssociativeArraysConverter, qui hérite de JsonConverter de la manière suivante:

public class AssociativeArraysConverter<T> : JsonConverter
    where T : IAssociateFieldNameResolver, new()
{
    private T m_FieldNameResolver;

    public AssociativeArraysConverter()
    {
        m_FieldNameResolver = new T();
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IEnumerable).IsAssignableFrom(objectType) &&
                !typeof(string).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IEnumerable collectionObj = value as IEnumerable;

        writer.WriteStartObject();

        foreach (object currObj in collectionObj)
        {
            writer.WritePropertyName(m_FieldNameResolver.ResolveFieldName(currObj));
            serializer.Serialize(writer, currObj);
        }

        writer.WriteEndObject();
    }
}

Et déclaré l'interface suivante:

public interface IAssociateFieldNameResolver
{
    string ResolveFieldName(object i_Object);
}

Il ne reste plus qu'à créer une classe qui implémente la fonction unique de IAssociateFieldNameResolver, qui accepte chaque élément de la collection et renvoie une chaîne basée sur cet objet, qui agira comme la clé de l'objet associatif de l'élément.

Un exemple pour une telle classe est:

public class ChannelContextFieldNameResolver : IAssociateFieldNameResolver
{
    public string ResolveFieldName(object i_Object)
    {
        return (i_Object as ChannelContext).NominalId;
    }
}
28
Mikey S.