Выборочное исключение свойства в одной сериализации JSON, но не в другой

у меня такой ПОКО

      public class Foo {
   public string PartitionKey => $"foo-{Bar}";
   public string Bar { get; set; }
}

Я храню этот POCO как сериализованный JSON в базе данных (в частности, Azure Cosmos DB) и делаю его доступным для клиентов через ASP.NET WebApi.

При сериализации этого документа для Cosmos DB мне нужен PartitionKey, поэтому я не могу полностью исключить его, используя[JsonIgnore]. Но я не хочу, чтобы он был включен в мой ответ API. Так как же проще всего этого добиться? В идеале я не хочу писать свой собственный JsonSerializer, но, возможно, использую какой-то пользовательский атрибут?

Ожидаемые результаты:

Для базы данных:

      {
  "partitionKey": "foo-alice",
  "bar": "alice"
}

Для ответа API:

      {
  "bar": "alice"
}

Использование .NET7 иSystem.Text.Json

1 ответ

У вас есть несколько вариантов выборочного исключения свойства во время сериализации.

Во-первых, поскольку свойство доступно только для чтения, вы можете установитьJsonSerializerOptions.IgnoreReadOnlyProperties = true:

      var options = new JsonSerializerOptions
{
    IgnoreReadOnlyProperties = true,
    // Add other options as required
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 
    WriteIndented = true,           
};

var json = JsonSerializer.Serialize(foo, options);

Это исключит все свойства только для чтения, включая .

Во-вторых, если вам нужно исключить только, но включить другие свойства, доступные только для чтения, или исключить изменяемые свойства, или иным образом нуждаться в большем контроле над тем, что сериализуется, вы можете добавить модификатор DefaultJsonTypeInfoResolver, чтобы исключить нежелательные члены.

Чтобы исключить свойство по имени, добавьте следующий метод расширения:

      public static partial class JsonSerializerExtensions
{
    public static DefaultJsonTypeInfoResolver Exclude(this DefaultJsonTypeInfoResolver resolver, Type type, params string [] membersToExclude)
    {
        if (resolver == null || membersToExclude == null)
            throw new ArgumentNullException();
        var membersToExcludeSet = membersToExclude.ToHashSet();
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeInfo.Kind == JsonTypeInfoKind.Object && type.IsAssignableFrom(typeInfo.Type)) // Or type == typeInfo.Type if you don't want to exclude from subtypes
                                       foreach (var property in typeInfo.Properties)
                                           if (property.GetMemberName() is {} name && membersToExcludeSet.Contains(name))
                                               property.ShouldSerialize = static (obj, value) => false;
                               });
        return resolver;
    }

    public static string? GetMemberName(this JsonPropertyInfo property) => (property.AttributeProvider as MemberInfo)?.Name;
}

И теперь вы сможете:

      var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        .Exclude(typeof(Foo), nameof(Foo.PartitionKey)),
    // Add other options as required
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 
    WriteIndented = true,           
};

var json = JsonSerializer.Serialize(foo, options);

Если вы хотите исключить по пользовательскому атрибуту, а не по имени, введите следующий метод расширения:

      public static partial class JsonSerializerExtensions
{
    public static DefaultJsonTypeInfoResolver ExcludeByAttribute<TAttribute>(this DefaultJsonTypeInfoResolver resolver) where TAttribute : System.Attribute
    {
        if (resolver == null)
            throw new ArgumentNullException();
        var attr = typeof(TAttribute);
        resolver.Modifiers.Add(typeInfo => 
                               {
                                   if (typeInfo.Kind == JsonTypeInfoKind.Object)
                                       foreach (var property in typeInfo.Properties)
                                           if (property.AttributeProvider?.IsDefined(attr, true) == true)
                                               property.ShouldSerialize = static (obj, value) => false;
                               });
        return resolver;
    }
}

Затем изменитеFooследующее:

      [System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class JsonExludeFromResponseAttribute : System.Attribute { }

public class Foo {
    [JsonExludeFromResponseAttribute]
    public string PartitionKey => $"foo-{Bar}";
    public string Bar { get; set; }
}

И исключить следующим образом:

      var options = new JsonSerializerOptions
{
    TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        .ExcludeByAttribute<JsonExludeFromResponseAttribute>(),
    // Add other options as required
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 
    WriteIndented = true,           
};

var json = JsonSerializer.Serialize(foo, options);

Примечания:

  • Настройка контракта — новая функция в .NET 7.

  • Если у вас много разных типов сPartitionKeyсвойство, которое вы всегда хотите исключить, вы можете передать вtypeof(object)как базовый класс, из которого следует исключить свойство.

Демонстрационная скрипка здесь.

Другие вопросы по тегам