CSharp · Console
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters;
using System.IO;
using System.Data;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
// https://stackoverflow.com/questions/48387063/jsonconverter-applied-to-class-causes-jsonconverter-applied-to-property-to-be-ig
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;
if (contract == null)
throw new JsonSerializationException(string.Format("Contract for type {0} is not a JsonObjectContract", objectType));
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
existingValue = existingValue ?? contract.DefaultCreator();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.PropertyName:
{
var propertyName = (string)reader.Value;
reader.ReadAndAssert();
var jsonProperty = FindProperty(contract, propertyName);
if (jsonProperty == null)
continue;
object itemValue;
if (jsonProperty.Converter != null && jsonProperty.Converter.CanRead)
{
itemValue = jsonProperty.Converter.ReadJson(reader, jsonProperty.PropertyType, jsonProperty.ValueProvider.GetValue(existingValue), serializer);
}
else
{
itemValue = serializer.Deserialize(reader, jsonProperty.PropertyType);
}
jsonProperty.ValueProvider.SetValue(existingValue, itemValue);
}
break;
case JsonToken.EndObject:
return existingValue;
default:
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)
{
// Remove snake-case underscore.
propertyName = propertyName.Replace("_", "");
// And do a case-insensitive match.
return contract.Properties.GetClosestMatchProperty(propertyName);
}
}
// This type should be applied via attributes.
public class FuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
// This type can be used in JsonSerializerSettings.Converters
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))
return false;
var contract = contractResolver.ResolveContract(objectType);
return contract is JsonObjectContract;
}
}
public static class JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected EOF!");
}
}
class RootObject
{
[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; }
}
class TestClass
{
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()
{
var json = @"{
""test_id"": ""hello"",
""minimum_distance"": 10101.1,
""tax_included"": ""yes"",
""is_mets_fan"": ""no""
}";
return json;
}
static string GetRootObjectJson()
{
var json = @"{
""some_value"": ""yes"",
""some_string"": ""hello"",
""some_children"": [
{
""some_value"": ""no"",
""some_string"": ""goodbye"",
""some_children"": null
}
]
}";
return json;
}
#if false
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();
}
#endif
}
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)))
return true;
if (Falsey.Any(t => t.Equals(b)))
return false;
throw new ArgumentException(string.Format("Unable to convert {0} into a Boolean attribute.", obj));
}
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// TODO: check that reader.TokenType is a primitive
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 class Program
{
public static void Main()
{
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}
public class AssertionFailedException : System.Exception
{
public AssertionFailedException() : base() { }
public AssertionFailedException(string s) : base(s) { }
}
public static class Assert
{
public static void IsTrue(bool value)
{
IsTrue(value, "failed");
}
public static void IsTrue(bool value, string message)
{
if (value == false)
throw new AssertionFailedException(message ?? "failed");
}
}