using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
[JsonConverter(typeof(TypedExtensionDataConverter<RootObject>))]
this.MetaData = new Dictionary<string, string>();
this.TimeSeries = new Dictionary<string, Dictionary<DateTime, TimeSeriesData>>();
[JsonProperty("Meta Data")]
public Dictionary<string, string> MetaData { get; set; }
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; }
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
public class TypedExtensionDataConverter<TObject> : JsonConverter
public override bool CanConvert(Type objectType)
return typeof(TObject).IsAssignableFrom(objectType);
JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
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)
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);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var extensionJsonProperty = GetExtensionJsonProperty(contract);
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());
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
public PushValue(T value, Func<T> getValue, Action<T> setValue)
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
#region IDisposable Members
public static class JsonExtensions
public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
public static IList<JToken> AsList(this IList<JToken> container) { return container; }
public static void Test()
Console.WriteLine("Input JSON:");
var root = JsonConvert.DeserializeObject<RootObject>(json);
var json2 = JsonConvert.SerializeObject(root, Formatting.Indented);
Console.WriteLine("Deserialized and re-serialized JSON:");
Console.WriteLine(json2);
""1. Information"": ""Intraday (1min) prices and volumes"",
""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"",
""4. close"": ""30.7000"",
""5. volume"": ""1390302""
""2017-05-30 15:59:00"": {
""1. open"": ""30.7750"",
""2. high"": ""30.7800"",
""4. close"": ""30.7250"",
""5. volume"": ""380134""
""Time Series (10min)"": {
""2017-05-30 16:00:00"": {
""1. open"": ""30.7200"",
""2. high"": ""30.7300"",
""4. close"": ""30.7000"",
""5. volume"": ""1390302""
""2017-05-30 15:59:00"": {
""1. open"": ""30.7750"",
""2. high"": ""30.7800"",
""4. close"": ""30.7250"",
""5. volume"": ""380134""
public static void Main()
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);