There is no way to make a single attribute that applies two different attributes simultaneously.[1] Instead, you could create a custom contract resolver that automatically serializes private fields marked with [SerializedField]
. First, define the following resolver:
public class IncludeSerializedFieldsResolver : DefaultContractResolver
{
// TODO: Check the interaction of [SerializedField] with memberSerialization = MemberSerialization.OptIn and MemberSerialization.Fields.
// TODO: Decide whether fields marked with [SerializedField] and [JsonIgnore] should be serialized. Currently they are not.
// TODO: Decide the interaction between [SerializedField] and data contract attributes -- https://www.newtonsoft.com/json/help/html/DataContractAndDataMember.htm
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member is FieldInfo f && !f.IsPublic
&& Attribute.IsDefined(f, typeof(SerializedFieldAttribute))
&& !Attribute.IsDefined(f, typeof(JsonIgnoreAttribute)))
{
property.Readable = property.Writable = true;
}
return property;
}
protected override List<MemberInfo> GetSerializableMembers(Type objectType)
{
var members = base.GetSerializableMembers(objectType);
var initialCount = members.Count;
var toAdd = objectType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => Attribute.IsDefined(f, typeof(SerializedFieldAttribute))
&& !Attribute.IsDefined(f, typeof(JsonIgnoreAttribute)))
.Where(f => members.IndexOf(f, 0, initialCount) < 0); // Don't add private members that already got added for some other reason, e.g. being marked with [JsonProperty]
members.AddRange(toAdd);
return members;
}
}
[System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
public class SerializedFieldAttribute : Attribute { }
Then serialize with the following settings:
// See https://www.newtonsoft.com/json/help/html/performance.htm#ReuseContractResolver
// For best performance you should cache and reuse this statically.
IContractResolver resolver = new IncludeSerializedFieldsResolver {
// Modify any properties as required, e.g.
// NamingStrategy = new CamelCaseNamingStrategy(),
};
var settings = new JsonSerializerSettings {
ContractResolver = resolver,
// Add other settings as required.
};
var json = JsonConvert.SerializeObject(rootModel, settings);
Notes:
Honestly I can't recommend doing this. Using a single attribute like [SerializedField]
to control multiple serializers would seem to violate the single responsibility principle, and might make your code harder to understand rather than easier. Plus you will have to apply [JsonProperty]
anyway to modify any of the 16 different JsonProperty
properties such as the property name, so you will probably end up having to apply it pretty often in practice even with the custom resolver.
The field will not be serialized if [JsonIgnore]
is also applied.
Newtonsoft recommends you statically cache and reuse contract resolvers for best performance.
You will get an exception trying to serialize a derived type with a serialized private field with the same name as a serialized private field in the base type. If this happens you will have to use a different JSON property name for one of the two fields.
Demo fiddle here.
[1] I checked the following, and none will do what you need:
C# does not support macros so you can't make a macro that applies both attributes.
JsonPropertyAttribute
is currently sealed, so you cannot make SerializedFieldAttribute
inherit from it.
The trick from this answer by Reza Aghaei to Combining multiple Attributes to a single Attribute - Merge Attributes will not work because Json.NET uses use reflection instead of type description to gather metadata.