Пользовательский сериализатор только для одного свойства в Json.NET без изменения класса модели - PullRequest
0 голосов
/ 13 декабря 2018

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

public class Person
{
    public string FirstName { get; set; }
    [JsonConverter(typeof(AllCapsConverter))]
    public string LastName { get; set; }
    // more properties here in the real example, some of which nest to properties that use their own JsonConverters.
}

JsonConverter для этого примера игрушки (содержание на самом деле не имеет значения; чтоуместно, что я использую это для свойства):

public class AllCapsConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
        => objectType == typeof(string);

    public override bool CanRead => false;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotSupportedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var str = value as string;
        var upper = str.ToUpperInvariant();
        JToken j = JToken.FromObject(upper);
        j.WriteTo(writer);
    }
}

Проходной юнит-тест:

public class PersonSerializationTest
{
    [Fact]
    public void SerializePerson_LastNameCaps()
    {
        var person = new Person
        {
            FirstName = "George",
            LastName = "Washington"
        };
        var serialized = JsonConvert.SerializeObject(person);
        var expected = @"{""FirstName"":""George"",""LastName"":""WASHINGTON""}";
        Assert.Equal(expected, serialized);
    }
}

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

Вы можете программно применить JsonConverter к одному или нескольким свойствам в классе модели без использования атрибутов через пользовательский ContractResolver.Вот простой пример, который применяет ваше AllCapsConverter к свойству LastName в вашем классе Person.(Если вы ищете более надежное решение, взгляните на @ 1006 * ответ @ dbc. Я намеревался показать самый простой пример, который мог бы сработать.)

public class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        if (prop.DeclaringType == typeof(Person) && prop.UnderlyingName == "LastName")
        {
            prop.Converter = new AllCapsConverter();
        }
        return prop;
    }
}

Вот обновленный тест и модель Person, которая показывает, как использовать преобразователь:

public class PersonSerializationTest
{
    [Fact]
    public void SerializePerson_LastNameCaps()
    {
        var person = new Person
        {
            FirstName = "George",
            LastName = "Washington"
        };
        var settings = new JsonSerializerSettings
        {
            ContractResolver = new CustomResolver()
        };
        var serialized = JsonConvert.SerializeObject(person, settings);
        var expected = @"{""FirstName"":""George"",""LastName"":""WASHINGTON""}";
        Assert.Equal(expected, serialized);
    }
}

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Рабочая демонстрация: https://dotnetfiddle.net/o4e3WP

0 голосов
/ 13 декабря 2018

Вы можете применить конвертеры к определенным свойствам, используя custom IContractResolver, унаследованный от DefaultContractResolver.

Сначала получите ConfigurableContractResolver из этого ответа в Как добавить метаданные для описания того, какие свойства являются датами в JSON.Net :

public class ConfigurableContractResolver : DefaultContractResolver
{
    // This contract resolver taken from the answer to
    // https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
    // https://stackoverflow.com/a/46083201/3744182

    readonly object contractCreatedPadlock = new object();
    event EventHandler<ContractCreatedEventArgs> contractCreated;
    int contractCount = 0;

    void OnContractCreated(JsonContract contract, Type objectType)
    {
        EventHandler<ContractCreatedEventArgs> created;
        lock (contractCreatedPadlock)
        {
            contractCount++;
            created = contractCreated;
        }
        if (created != null)
        {
            created(this, new ContractCreatedEventArgs(contract, objectType));
        }
    }

    public event EventHandler<ContractCreatedEventArgs> ContractCreated
    {
        add
        {
            lock (contractCreatedPadlock)
            {
                if (contractCount > 0)
                {
                    throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
                }
                contractCreated += value;
            }
        }
        remove
        {
            lock (contractCreatedPadlock)
            {
                if (contractCount > 0)
                {
                    throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
                }
                contractCreated -= value;
            }
        }
    }

    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);
        OnContractCreated(contract, objectType);
        return contract;
    }
}

public class ContractCreatedEventArgs : EventArgs
{
    public JsonContract Contract { get; private set; }
    public Type ObjectType { get; private set; }

    public ContractCreatedEventArgs(JsonContract contract, Type objectType)
    {
        this.Contract = contract;
        this.ObjectType = objectType;
    }
}

public static class ConfigurableContractResolverExtensions
{
    public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
    {
        if (resolver == null || handler == null)
            throw new ArgumentNullException();
        resolver.ContractCreated += handler;
        return resolver;
    }
}

Затем создайте метод для настройки JsonObjectContract для Person следующим образом:

public static class JsonContractExtensions
{
    public static void ConfigurePerson(this JsonContract contract)
    {
        if (!typeof(Person).IsAssignableFrom(contract.UnderlyingType))
            return;
        var objectContract = contract as JsonObjectContract;
        if (objectContract == null)
            return;
        var property = objectContract.Properties.Where(p => p.UnderlyingName == nameof(Person.LastName)).Single();
        property.Converter = new AllCapsConverter();
    }
}

И, наконец, сериализация следующим образом:

// Cache the contract resolver statically for best performance.
var resolver = new ConfigurableContractResolver()
    .Configure((s, e) => { e.Contract.ConfigurePerson(); });

var settigs = new JsonSerializerSettings
{
    ContractResolver = resolver,
};

var person = new Person
{
    FirstName = "George",
    LastName = "Washington"
};
var serialized = JsonConvert.SerializeObject(person, settigs);

Примечания:

  • Вместо создания ConfigurableContractResolver itбыло бы возможно создать подкласс DefaultContractResolver, переопределить DefaultContractResolver.CreateProperty и жестко закодировать необходимую логику для Person.LastName.Однако создание настраиваемого распознавателя, позволяющего объединять настройки во время выполнения, представляется более полезным и многократно используемым.

  • В AllCapsConverter.WriteJson() было бы проще использовать writer.WriteValue(string) для записи строки в верхнем регистре:

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var upper = ((string)value).ToUpperInvariant();
        writer.WriteValue(upper);
    }
    
  • Возможно, вы захотите кэшировать решатель контрактов для лучшей производительности.

Образец скрипки здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...