Пользовательское преобразование определенных объектов в JSON.NET - PullRequest
31 голосов
/ 20 июня 2011

Я использую 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 по умолчанию преобразует просто отлично.

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

ОБНОВЛЕНИЕ:

Я нашел, как создать новый пользовательский конвертер (создав новый класс, который наследует от 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 Ответ

28 голосов
/ 02 июля 2011

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

При сериализации определенных коллекций я хотел создать ассоциативный массив 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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...