using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public abstract class FuzzyMatchingJsonConverterBase : JsonConverter
protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
throw new JsonSerializationException(string.Format("Contract for type {0} is not a JsonObjectContract", objectType));
if (reader.TokenType == JsonToken.Null)
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
existingValue = existingValue ?? contract.DefaultCreator();
switch (reader.TokenType)
case JsonToken.PropertyName:
var propertyName = (string)reader.Value;
var jsonProperty = FindProperty(contract, propertyName);
if (jsonProperty == null)
if (jsonProperty.Converter != null && jsonProperty.Converter.CanRead)
itemValue = jsonProperty.Converter.ReadJson(reader, jsonProperty.PropertyType, jsonProperty.ValueProvider.GetValue(existingValue), serializer);
itemValue = serializer.Deserialize(reader, jsonProperty.PropertyType);
jsonProperty.ValueProvider.SetValue(existingValue, itemValue);
case JsonToken.EndObject:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
throw new JsonReaderException("Unexpected EOF!");
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
public abstract class FuzzySnakeCaseMatchingJsonConverterBase : FuzzyMatchingJsonConverterBase
protected override JsonProperty FindProperty(JsonObjectContract contract, string propertyName)
propertyName = propertyName.Replace("_", "");
return contract.Properties.GetClosestMatchProperty(propertyName);
public class FuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
public override bool CanConvert(Type objectType)
throw new NotImplementedException();
public class GlobalFuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
readonly IContractResolver contractResolver;
public GlobalFuzzySnakeCaseMatchingJsonConverter(IContractResolver contractResolver)
this.contractResolver = contractResolver;
public override bool CanConvert(Type objectType)
if (objectType.IsPrimitive || objectType == typeof(string))
var contract = contractResolver.ResolveContract(objectType);
return contract is JsonObjectContract;
public static class JsonReaderExtensions
public static void ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected EOF!");
[JsonConverter(typeof(BooleanJsonConverter))]
public bool SomeValue { get; set; }
public string SomeString { get; set; }
public List<RootObject> SomeChildren { get; set; }
[JsonConverter(typeof(FuzzySnakeCaseMatchingJsonConverter))]
public class JsonTestData
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
public static void Test()
Test<JsonTestData>(GetJsonTestDataJson(), null);
var resolver = new DefaultContractResolver();
var settings = new JsonSerializerSettings
ContractResolver = resolver,
Converters = { new GlobalFuzzySnakeCaseMatchingJsonConverter(resolver) },
Test<RootObject>(GetRootObjectJson(), settings);
Console.WriteLine("\nDone tests.");
static void Test<JsonTestData>(string inputJson, JsonSerializerSettings settings)
Console.WriteLine("\nInput JSON:\n{0}", inputJson);
var root = JsonConvert.DeserializeObject<JsonTestData>(inputJson, settings);
var json2 = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
Console.WriteLine("Deserialized and re-serialized {0}\n{1}", root, json2);
static string GetJsonTestDataJson()
""minimum_distance"": 10101.1,
""tax_included"": ""yes"",
static string GetRootObjectJson()
""some_string"": ""hello"",
""some_string"": ""goodbye"",
static string GenerateTestJson<T>(T root)
var jtoken = JObject.FromObject(root, JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() } }));
foreach (var property in jtoken.Descendants().OfType<JProperty>().Where(p => p.Value.Type == JTokenType.Boolean))
property.Value = (JValue)((bool)(JValue)(property.Value) ? "yes" : "no");
return jtoken.ToString();
public class BooleanJsonConverter : JsonConverter
private static readonly string[] Truthy = new[] { "t", "true", "y", "yes", "1" };
private static readonly string[] Falsey = new[] { "f", "false", "n", "no", "0" };
public static Func<object, bool> ParseBoolean
= (obj) => { var b = (obj ?? "").ToString().ToLower().Trim(); return Truthy.Any(t => t.Equals(b)); };
public static bool ParseBooleanWithValidation(object obj)
var b = (obj ?? "").ToString().ToLower().Trim();
if (Truthy.Any(t => t.Equals(b)))
if (Falsey.Any(t => t.Equals(b)))
throw new ArgumentException(string.Format("Unable to convert {0} into a Boolean attribute.", obj));
public override bool CanConvert(Type objectType)
return objectType == typeof(bool);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
return ParseBooleanWithValidation(reader.Value);
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
public static void Main()
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public class AssertionFailedException : System.Exception
public AssertionFailedException() : base() { }
public AssertionFailedException(string s) : base(s) { }
public static class Assert
public static void IsTrue(bool value)
public static void IsTrue(bool value, string message)
throw new AssertionFailedException(message ?? "failed");