questions/48387063/jsonconverter-applied-to-class-causes-jsonconverter-applied-to-property-to-be-ig

CSharp · Console

/Program.cs

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");
        }
    }
;