Пользовательское преобразование определенных объектов в JSON.NET

Я использую JSON.NET для сериализации некоторых из моих объектов, и я хотел бы знать, есть ли простой способ переопределить конвертер json.net по умолчанию только для определенного объекта?

В настоящее время у меня есть следующий класс:

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

В настоящее время JSON.NET выполняет сериализацию выше:

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

Возможно ли, чтобы этот конкретный класс форматировал его следующим образом:

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

Я как-то новичок в JSON.NET. Мне было интересно, имеет ли это что-то общее с написанием пользовательского конвертера. Я не смог найти конкретных примеров того, как писать один. Если кто-то может указать мне на какой-то конкретный источник, я буду очень признателен.

Мне нужно найти решение, которое заставляет этот конкретный класс всегда конвертировать то же самое, потому что вышеупомянутый контекст является частью еще большего контекста, который конвертер по умолчанию JSON.NET конвертирует только отлично.

Надеюсь, мой вопрос достаточно ясен...

UPDATE:

Я нашел, как создать новый настраиваемый конвертер (создав новый класс, который наследует JsonConverter и переопределяет его абстрактные методы), я переопределяю метод WriteJson следующим образом:

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

Это действительно успешно работает, но... Я заинтригован, если есть способ сериализовать остальные свойства объекта, повторно используя JsonSerializer по умолчанию (или конвертер, если на то пошло) вместо того, чтобы вручную "Написание" объекта с помощью методов jsonwriter.

ОБНОВЛЕНИЕ 2: Я пытаюсь получить более общее решение и придумал следующее:

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

Это отлично работает при добавлении конвертера вручную в сериализатор, например:

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

Но не работает при использовании атрибута [JsonConverter()], установленного в моем пользовательском обложке над классом ChannelContext из-за цикла самоопределения, который возникает при выполнении:

serializer.Serialize(writer, value)

Это, очевидно, потому, что мой пользовательский конвертер теперь считается конвертером по умолчанию для класса, когда он установлен с JsonConverterAttribute, поэтому я получаю замкнутый цикл. Единственное, что я могу придумать, чтобы решить эту проблему, наследуется от базового класса jsonconverter и вызывает метод base.serialize() вместо этого... Но существует ли такой класс JsonConverter?

Спасибо большое!

Мики

Ответ 1

Если кто-то заинтересован в моем решении:

При сериализации определенных коллекций я хотел создать ассоциативный json-массив вместо стандартного json-массива, поэтому мой коллега-клиент-разработчик может эффективно использовать эти поля, используя их имя (или ключ в этом отношении) вместо повторения через них.

рассмотрим следующее:

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

Контекст ответа содержит весь ответ, который записывается обратно клиенту, как вы можете видеть, он включает в себя раздел, называемый "каналы". Вместо того, чтобы выводить канальные контексты в обычном массиве, я хотел бы иметь возможность вывод следующим образом:

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

Так как я хотел использовать вышеприведенное для других контекстов, и я мог бы решить использовать другое свойство в качестве своего "ключа" или даже захотел бы создать свое собственное уникальное имя, которое не имеет отношения к любое свойство, мне понадобилось какое-то общее решение, поэтому я написал общий класс AssociativeArraysConverter, который наследует от JsonConverter следующим образом:

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

И объявлен следующий интерфейс:

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

Теперь все осталось сделать, это создать класс, который реализует одиночную функцию IAssociateFieldNameResolver, которая принимает каждый элемент в коллекции и возвращает строку на основе этого объекта, которая будет действовать как элемент ассоциативного объекта элемента.

Пример для такого класса:

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