I have a POCO like this
public class Foo {
public string PartitionKey => $"foo-{Bar}";
public string Bar { get; set; }
}
I'm storing that POCO as serialized JSON in a database (Azure Cosmos DB to be specific) and making it available to clients via a ASP.NET WebApi.
When serializing that document for Cosmos DB, I need the PartitionKey, so I cannot exclude it totally by using [JsonIgnore]
. But I do not want it included in my API response. So what is the easiest way to achieve this? Ideally I don't want to write my own JsonSerializer, but maybe using some custom attribute?
Expected outcomes:
For the database:
{
"partitionKey": "foo-alice",
"bar": "alice"
}
For the api response:
{
"bar": "alice"
}
Using .NET7 and System.Text.Json
CodePudding user response:
You have a couple options to selectively exclude the PartitionKey
property during serialization.
Firstly, since the property is read-only, you could set 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);
This will exclude all read-only properties including PartitionKey
.
Secondly, if you need to exclude only PartitionKey
but include other read-only properties, or exclude modifiable properties, or otherwise need more control over what gets serialized, you can add a DefaultJsonTypeInfoResolver modifier to exclude the unwanted members.
To exclude the property by name, add the following extension method:
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;
}
And now you will be able to do:
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);
If you want to exclude by custom attribute rather than by name, introduce the following extension method:
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;
}
}
Then modify Foo
as follows:
[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; }
}
And exclude PartitionKey
as follows:
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);
Notes:
Contract customization is new in .NET 7.
If you have many different types with a
PartitionKey
property which you always want to exclude, you could pass intypeof(object)
as the base class from which to exclude the property.
Demo fiddle here.