questions/47578150/json-array-to-custom-object-mapping-in-c-sharp

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/47578150/json-array-to-custom-object-mapping-in-c-sharp
    public class Page
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string Error { get; set; }
    }

    public class Label
    {
        public string DisplayName { get; set; }
        public string Value { get; set; }
    }

    public class RootObject
    {
        public List<Page> pages { get; set; }
        public List<Label> labels { get; set; }
    }

    public class PolymorphicArrayToObjectConverter<TObject> : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(TObject).IsAssignableFrom(objectType);
        }

        public override bool CanWrite { get { return false; } }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        static JsonObjectContract FindContract(JObject obj, IEnumerable<Type> derivedTypes, JsonSerializer serializer)
        {
            List<JsonObjectContract> bestContracts = new List<JsonObjectContract>();
            foreach (var type in derivedTypes)
            {
                if (type.IsAbstract)
                    continue;
                var contract = serializer.ContractResolver.ResolveContract(type) as JsonObjectContract;
                if (contract == null)
                    continue;
                if (obj.Properties().Select(p => p.Name).Any(n => contract.Properties.GetClosestMatchProperty(n) == null))
                    continue;
                if (bestContracts.Count == 0 || bestContracts[0].Properties.Count > contract.Properties.Count)
                {
                    bestContracts.Clear();
                    bestContracts.Add(contract);
                }
                else if (contract.Properties.Count == bestContracts[0].Properties.Count)
                {
                    bestContracts.Add(contract);
                }
            }
            return bestContracts.Single();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            else if (reader.TokenType != JsonToken.StartArray)
                throw new InvalidOperationException("JSON token is not an array at path: " + reader.Path);

            var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
            existingValue = existingValue ?? contract.DefaultCreator();

            var lookup = contract
                .Properties
                .Select(p => new { Property = p, PropertyContract = serializer.ContractResolver.ResolveContract(p.PropertyType) as JsonArrayContract })
                .Where(i => i.PropertyContract != null)
                .ToDictionary(i => i.PropertyContract.CollectionItemType);
            var types = lookup.Select(i => i.Key).ToList();

            while (reader.Read())
            {
                switch (reader.TokenType)
                {
                    case JsonToken.Comment:
                        break;
                    case JsonToken.EndArray:
                        return existingValue;
                    default:
                        {
                            var itemObj = JObject.Load(reader);
                            var itemContract = FindContract(itemObj, types, serializer);
                            if (itemContract == null)
                                continue;
                            var item = serializer.Deserialize(itemObj.CreateReader(), itemContract.UnderlyingType);
                            var propertyData = lookup[itemContract.UnderlyingType];
                            var collection = propertyData.Property.ValueProvider.GetValue(existingValue);
                            if (collection == null)
                            {
                                collection = propertyData.PropertyContract.DefaultCreator();
                                propertyData.Property.ValueProvider.SetValue(existingValue, collection);
                            }
                            collection.GetType().GetMethod("Add").Invoke(collection, new [] { item });
                        }
                        break;
                }
            }
            // Should not come here.
            throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
        }
    }

    class TestClass
    {
        public static void Test()
        {
            var jsonString = GetJson();

            var settings = new JsonSerializerSettings
            {
                Converters = { new PolymorphicArrayToObjectConverter<RootObject>() },
            };
            var root = new List<RootObject> { JsonConvert.DeserializeObject<RootObject>(jsonString, settings) };

            var outputJson = JsonConvert.SerializeObject(root, Formatting.Indented);

            Console.WriteLine("Result of deserializing and re-serializing the JSON: ");
            Console.WriteLine(outputJson);

            Assert.IsTrue(JToken.DeepEquals(JToken.Parse(outputJson), JToken.Parse(GetDesiredJson())));

            Console.WriteLine("Result is identical to desired JSON.");
        }

		
        static string GetJson()
        {
            var json = @"[
    {
        ""FirstName"": ""Test1"",
        ""LastName"": ""Test2"",
        ""Address"": ""London, GB"",
        ""Error"": ""Something's gone wrong""
    },
    {
        ""FirstName"": ""Test3"",
        ""LastName"": ""Test4"",
        ""Address"": ""NewYork, US"",
        ""Error"": ""Something's gone wrong""
    },
    {
        ""DisplayName"": ""ContactNumber"",
        ""Value"": ""01234 123 123""
    }
]";
            return json;
        }

        static string GetDesiredJson()
        {
            var json = @"[{
	""pages"": [{
			""FirstName"": ""Test1"",
			""LastName"": ""Test2"",
			""Address"": ""London, GB"",
			""Error"": ""Something's gone wrong""
		},
		{
			""FirstName"": ""Test3"",
			""LastName"": ""Test4"",
			""Address"": ""NewYork, US"",
			""Error"": ""Something's gone wrong""
		}
	],

	""labels"": [{
		""DisplayName"": ""ContactNumber"",
		""Value"": ""01234 123 123""
	}]
}]
";
            return json;
        }
    }


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