using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class CustomTypeAttribute : System.Attribute
public CustomTypeAttribute(string type) => this.Type = type;
public string Type { get; set; }
public class ObjectAsAllPropsConverter<TBase> : JsonConverter
const string AllPropsName = "AllProps";
const string KeyName = "Key";
const string ValueName = "Value";
const string TypeName = "Type";
const string DefaultType = "0";
static IContractResolver DefaultResolver { get; } = JsonSerializer.CreateDefault().ContractResolver;
readonly IContractResolver resolver;
public ObjectAsAllPropsConverter() : this(DefaultResolver) { }
public ObjectAsAllPropsConverter(IContractResolver resolver) => this.resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
public override bool CanConvert(Type objectType)
if (objectType.IsPrimitive || objectType == typeof(string) || !typeof(TBase).IsAssignableFrom(objectType))
return resolver.ResolveContract(objectType) is JsonObjectContract;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
writer.WriteStartObject();
writer.WritePropertyName(AllPropsName);
writer.WriteStartArray();
foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
var propertyValue = property.ValueProvider.GetValue(value);
if (propertyValue == null && (serializer.NullValueHandling == NullValueHandling.Ignore || property.NullValueHandling == NullValueHandling.Ignore))
writer.WriteStartObject();
writer.WritePropertyName(KeyName);
writer.WriteValue(property.PropertyName);
writer.WritePropertyName(ValueName);
if (propertyValue == null)
else if (property.Converter != null && property.Converter.CanWrite)
property.Converter.WriteJson(writer, propertyValue, serializer);
serializer.Serialize(writer, propertyValue);
writer.WritePropertyName(TypeName);
var type = property.AttributeProvider.GetAttributes(typeof(CustomTypeAttribute), true).Cast<CustomTypeAttribute>().SingleOrDefault()?.Type ?? DefaultType;
protected virtual bool ShouldSerialize(JsonProperty property, object value) =>
property.Readable && !property.Ignored && (property.ShouldSerialize == null || property.ShouldSerialize(value));
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public partial class MyClass
public string Prop1 { get; set; }
[CustomType("somevalue")]
public string Prop2 { get; set; }
[CustomType("anothervalue")]
public string PropN { get; set; }
public partial class MyClass
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(TestColorConverter))]
public Color? Color { get; set; }
[CustomType("ListOfStrings")]
public List<string> List { get; } = new ();
public bool ShouldSerializeList() => List?.Count > 0;
public static void Test()
var myClass = new MyClass
Prop1 = "value of prop1",
Prop2 = "value of prop2",
PropN = "value of propn",
var settings = new JsonSerializerSettings
Converters = { new ObjectAsAllPropsConverter<object>() },
var json = JsonConvert.SerializeObject(myClass, Formatting.Indented, settings);
var token = JObject.FromObject(myClass, JsonSerializer.CreateDefault(settings));
Assert.IsTrue(JToken.DeepEquals(JToken.Parse(GetRequiredJson()), token));
myClass.Color = Color.AliceBlue;
myClass.List.Add("Hello");
var json2 = JsonConvert.SerializeObject(myClass, Formatting.Indented, settings);
Console.WriteLine(json2);
Assert.IsTrue(json2.Contains(myClass.Color.Value.ToHtml()));
static string GetRequiredJson() => @" {
""Value"": ""value of prop1"",
""Value"": ""value of prop2"",
""Value"": ""value of propn"",
""Type"": ""anothervalue""
public static class ColorExtensions
public static string ToHtml(this Color c)
return string.Format("rgba({0},{1},{2},{3})", c.R, c.G, c.B, c.A / 255f);
public class TestColorConverter : JsonConverter
public override bool CanConvert(Type objectType) => objectType == typeof(Color) || objectType == typeof(Color?);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
writer.WriteValue(c.ToHtml());
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];