CSharp · Console
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using NUnit.Framework; // Throws NUnit.Framework.AssertionException
//https://stackoverflow.com/questions/76987746/how-would-i-create-a-custom-attribute-that-uses-another
#if false
There is no way to do what you want out of the box with Json.NET because
- As noted by Marc Gravell, [JsonPropertyAttribute](https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPropertyAttribute.cs#L35) is sealed so you cannot make your attribute inherit from it.
- The trick from [this answer](https://stackoverflow.com/a/38504552) by [Reza Aghaei](https://stackoverflow.com/a/38504552) to [Combining multiple Attributes to a single Attribute - Merge Attributes](https://stackoverflow.com/q/38503146) will not work because Json.NET uses use reflection instead of type description to gather metadata.
As a workaround, you could create a [custom contract resolver](https://www.newtonsoft.com/json/help/html/contractresolver.htm#CustomIContractResolverExamples) that adds all fields marked with your `SerializedFieldAttribute` into the serialization contract.
#endif
public class MyClass
{
[SerializedField]
private int colliderType = 0;
[SerializedField]
[JsonIgnore]
private int ignoreMe = 0;
public void SetColliderType(int type) => colliderType = type;
}
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 { }
class TestClass
{
public static void Test()
{
var rootModel = new MyClass();
rootModel.SetColliderType(22);
// 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);
Console.WriteLine(json);
Assert.AreEqual(json, """{"colliderType":22}""");
var myClass2 = JsonConvert.DeserializeObject<MyClass>(json, settings);
var json2 = JsonConvert.SerializeObject(myClass2, settings);
Assert.AreEqual(json2, """{"colliderType":22}""");
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Namespace, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}
//Console.WriteLine("Created property {0}, Ignored={1}, Readable={2}, Writable={3}", property, property.Ignored, property.Readable, property.Writable);
//Console.WriteLine("Returned members: {0}", string.Join(",", members.Select(m => m.Name).ToArray()));