using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class AlternateContractResolverConverter : JsonConverter
static Stack<Type> contractResolverTypeStack;
static Stack<Type> ContractResolverTypeStack { get { return contractResolverTypeStack = (contractResolverTypeStack ?? new Stack<Type>()); } }
readonly IContractResolver resolver;
JsonSerializerSettings ExtractAndOverrideSettings(JsonSerializer serializer)
var settings = serializer.ExtractSettings();
settings.ContractResolver = resolver;
settings.CheckAdditionalContent = false;
if (settings.PreserveReferencesHandling != PreserveReferencesHandling.None)
Debug.WriteLine(string.Format("PreserveReferencesHandling.{0} not supported", serializer.PreserveReferencesHandling));
public AlternateContractResolverConverter(Type resolverType)
if (resolverType == null)
throw new ArgumentNullException("resolverType");
resolver = (IContractResolver)Activator.CreateInstance(resolverType);
throw new ArgumentNullException(string.Format("Resolver type {0} not found", resolverType));
public override bool CanRead { get { return !ContractResolverTypeStack.TryPeek(out var resolverType) || resolverType != resolver.GetType(); } }
public override bool CanWrite { get { return !ContractResolverTypeStack.TryPeek(out var resolverType) || resolverType != resolver.GetType(); } }
public override bool CanConvert(Type objectType)
throw new NotImplementedException("This contract resolver is intended to be applied directly with [JsonConverter(typeof(AlternateContractResolverConverter), typeof(SomeContractResolver))] or [JsonProperty(ItemConverterType = typeof(AlternateContractResolverConverter), ItemConverterParameters = ...)]");
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
return JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Deserialize(reader, objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
using (ContractResolverTypeStack.PushUsing(resolver.GetType()))
JsonSerializer.CreateDefault(ExtractAndOverrideSettings(serializer)).Serialize(writer, value);
internal static class JsonSerializerExtensions
public static JsonSerializerSettings ExtractSettings(this JsonSerializer serializer)
throw new ArgumentNullException("serializer");
var settings = new JsonSerializerSettings
SerializationBinder = serializer.SerializationBinder,
CheckAdditionalContent = serializer.CheckAdditionalContent,
ConstructorHandling = serializer.ConstructorHandling,
ContractResolver = serializer.ContractResolver,
Converters = serializer.Converters,
Context = serializer.Context,
Culture = serializer.Culture,
DateFormatHandling = serializer.DateFormatHandling,
DateFormatString = serializer.DateFormatString,
DateParseHandling = serializer.DateParseHandling,
DateTimeZoneHandling = serializer.DateTimeZoneHandling,
DefaultValueHandling = serializer.DefaultValueHandling,
EqualityComparer = serializer.EqualityComparer,
FloatFormatHandling = serializer.FloatFormatHandling,
FloatParseHandling = serializer.FloatParseHandling,
Formatting = serializer.Formatting,
MaxDepth = serializer.MaxDepth,
MetadataPropertyHandling = serializer.MetadataPropertyHandling,
MissingMemberHandling = serializer.MissingMemberHandling,
NullValueHandling = serializer.NullValueHandling,
ObjectCreationHandling = serializer.ObjectCreationHandling,
ReferenceLoopHandling = serializer.ReferenceLoopHandling,
ReferenceResolverProvider = () => serializer.ReferenceResolver,
PreserveReferencesHandling = serializer.PreserveReferencesHandling,
StringEscapeHandling = serializer.StringEscapeHandling,
TraceWriter = serializer.TraceWriter,
TypeNameAssemblyFormatHandling = serializer.TypeNameAssemblyFormatHandling,
TypeNameHandling = serializer.TypeNameHandling,
public static class StackExtensions
public struct PushValue<T> : IDisposable
public PushValue(T value, Stack<T> stack)
public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
throw new ArgumentNullException();
return new PushValue<T>(value, stack);
public class KebabCaseContractResolver : DefaultContractResolver
public KebabCaseContractResolver()
NamingStrategy = new KebabCaseNamingStrategy();
[JsonConverter(typeof(AlternateContractResolverConverter), typeof(KebabCaseContractResolver))]
public Name FullName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public static void Test()
var json = JsonConvert.SerializeObject(new { PersonContainerNotInKebabCase = person }, Formatting.Indented);
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(typeof(SnakeCaseNamingStrategy));
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];
`[JsonObject(NamingStrategyType = typeof(KebabCaseNamingStrategy))]` only applies to properties declared by that type. It (apparently) doesn't apply to base class properties, and definitely doesn't recursively to properties of referenced objects in the serialization graph.
To change naming strategy for an object and all nested objects, see [JSON .NET Custom Name Resolver for Sub-Properties](https:
The correct usage would be `[JsonConverter(typeof(AlternateContractResolverConverter), typeof(KebabCaseContractResolver))]` not `[JsonConverter(typeof(KebabCaseContractResolver))]` because a contract resolver is not a converter. But it turns out that `AlternateContractResolverConverter` had a bug or limitation making it not work when applied to a type instead of a property. You can see a fixed version here: https: