CSharp · Console
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, TimeSeriesData>>();
}
[JsonProperty("Meta Data")]
public Dictionary<string, string> MetaData { get; set; }
[JsonTypedExtensionData]
public Dictionary<string, Dictionary<DateTime, TimeSeriesData>> TimeSeries { get; set; }
}
public class TimeSeriesData
{
[JsonProperty("1. open")]
public decimal Open { get; set; }
[JsonProperty("2. high")]
public decimal High { get; set; }
[JsonProperty("3. low")]
public decimal Low { get; set; }
[JsonProperty("4. close")]
public decimal Close { get; set; }
[JsonProperty("5. volume")]
public decimal Volume { 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();
}
}