questions/44863460/parsing-complex-json-with-c-sharp

CSharp · Console

/Program.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Net;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;


// https://stackoverflow.com/questions/44863460/parsing-complex-json-with-c-sharp

    [JsonConverter(typeof(TypedExtensionDataConverter<RootObject>))]
    public class RootObject
    {
        public RootObject()
        {
            // Ensure dictionaries are allocated.
            this.MetaData = new Dictionary<string, string>();
            this.TimeSeries = new Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>>();
        }

        [JsonProperty("Meta Data")]
        public Dictionary<string, string> MetaData { get; set; }

        [JsonTypedExtensionData]
        public Dictionary<string, Dictionary<DateTime, Dictionary<string, decimal>>> TimeSeries { get; set; }
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
    public class JsonTypedExtensionDataAttribute : Attribute
    {
    }

    // Converter and attribute from 
    // https://stackoverflow.com/questions/40088941/how-to-deserialize-a-child-object-with-dynamic-numeric-key-names

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

        JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
        {
            try
            {
                return contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonTypedExtensionDataAttribute), false).Any()).Single();
            }
            catch (InvalidOperationException ex)
            {
                throw new JsonSerializationException(string.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex);
            }
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            var jObj = JObject.Load(reader);
            var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
            var extensionJsonProperty = GetExtensionJsonProperty(contract);

            var extensionJProperty = (JProperty)null;
            for (int i = jObj.Count - 1; i >= 0; i--)
            {
                var property = (JProperty)jObj.AsList()[i];
                if (contract.Properties.GetClosestMatchProperty(property.Name) == null)
                {
                    if (extensionJProperty == null)
                    {
                        extensionJProperty = new JProperty(extensionJsonProperty.PropertyName, new JObject());
                        jObj.Add(extensionJProperty);
                    }
                    ((JObject)extensionJProperty.Value).Add(property.RemoveFromLowestPossibleParent());
                }
            }

            var value = existingValue ?? contract.DefaultCreator();
            using (var subReader = jObj.CreateReader())
                serializer.Populate(subReader, value);
            return value;
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
            var extensionJsonProperty = GetExtensionJsonProperty(contract);

            JObject jObj;
            using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
            {
                jObj = JObject.FromObject(value, serializer);
            }

            var extensionValue = (jObj[extensionJsonProperty.PropertyName] as JObject).RemoveFromLowestPossibleParent();
            if (extensionValue != null)
            {
                for (int i = extensionValue.Count - 1; i >= 0; i--)
                {
                    var property = (JProperty)extensionValue.AsList()[i];
                    jObj.Add(property.RemoveFromLowestPossibleParent());
                }
            }

            jObj.WriteTo(writer);
        }

        [ThreadStatic]
        static bool disabled;

        // Disables the converter in a thread-safe manner.
        bool Disabled { get { return disabled; } set { disabled = value; } }

        public override bool CanWrite { get { return !Disabled; } }

        public override bool CanRead { get { return !Disabled; } }
    }

    public struct PushValue<T> : IDisposable
    {
        Action<T> setValue;
        T oldValue;

        public PushValue(T value, Func<T> getValue, Action<T> setValue)
        {
            if (getValue == null || setValue == null)
                throw new ArgumentNullException();
            this.setValue = setValue;
            this.oldValue = getValue();
            setValue(value);
        }

        #region IDisposable Members

        // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
        public void Dispose()
        {
            if (setValue != null)
                setValue(oldValue);
        }

        #endregion
    }

    public static class JsonExtensions
    {
        public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
        {
            if (node == null)
                return null;
            var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
            if (contained != null)
                contained.Remove();
            // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
            if (node.Parent is JProperty)
                ((JProperty)node.Parent).Value = null;
            return node;
        }

        public static IList<JToken> AsList(this IList<JToken> container) { return container; }
    }

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

            Console.WriteLine("Input JSON:");
            Console.WriteLine(json);
            Console.WriteLine();

            var root = JsonConvert.DeserializeObject<RootObject>(json);

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

            Console.WriteLine("Deserialized and re-serialized JSON:");
            Console.WriteLine(json2);
            Console.WriteLine("");
        }

        static string GetJson()
        {
            var json = @"{
  ""Meta Data"": {
    ""1. Information"": ""Intraday (1min) prices and volumes"",
    ""2. Symbol"": ""MU"",
    ""3. Last Refreshed"": ""2017-05-30 16:00:00"",
    ""4. Interval"": ""1min"",
    ""5. Output Size"": ""Full size"",
    ""6. Time Zone"": ""US/Eastern""
  },
  ""Time Series (1min)"": {
    ""2017-05-30 16:00:00"": {
      ""1. open"": ""30.7200"",
      ""2. high"": ""30.7300"",
      ""3. low"": ""30.7000"",
      ""4. close"": ""30.7000"",
      ""5. volume"": ""1390302""
    },
    ""2017-05-30 15:59:00"": {
      ""1. open"": ""30.7750"",
      ""2. high"": ""30.7800"",
      ""3. low"": ""30.7200"",
      ""4. close"": ""30.7250"",
      ""5. volume"": ""380134""
    }
  },
  ""Time Series (10min)"": {
    ""2017-05-30 16:00:00"": {
      ""1. open"": ""30.7200"",
      ""2. high"": ""30.7300"",
      ""3. low"": ""30.7000"",
      ""4. close"": ""30.7000"",
      ""5. volume"": ""1390302""
    },
    ""2017-05-30 15:59:00"": {
      ""1. open"": ""30.7750"",
      ""2. high"": ""30.7800"",
      ""3. low"": ""30.7200"",
      ""4. close"": ""30.7250"",
      ""5. volume"": ""380134""
    }
  }
}";
            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("");
	
			TestClass.Test();
		}
	}
;