user2901304
user2901304

Reputation: 713

Creating JSON with dynamic keys in c#

I am trying to replicate the following JSON structure:

{"result_content": {
    "data": {
         "city_name" : "Beverly Hills",
         "2014-06-05T00:00:00": {
         "morning_low": "20",
         "daytime_high": "40"
         },
         "2014-06-06T00:00:00": {
         "morning_low": "21",
         "daytime_high": "41"
         },
         "2014-06-07T00:00:00": {
         "morning_low": "22",
         "daytime_high": "42"
         },
        "2014-06-08T00:00:00": {
        "morning_low": "23",
        "daytime_high": "43"
        },
        "2014-06-09T00:00:00": {
        "morning_low": "24",
        "daytime_high": "44"
        }
      }
    }
  }

But I can't figure out how create the keys to be dynamic using C#.

Here are my object class's

public class Day
{
    public string morning_low { get; set; }
    public string daytime_high { get; set; }
}

public class Data
{
    public string city_name { get; set; }
    public List<Day> days { get; set; }
}

public class ResultContent
{
    public Data data { get; set; }
}

And here is how im building it all:

            ResultContent content = new ResultContent();
            content.data = new Data();
            content.data.city_name = results.Body.GetCityForecastByZIPResponse.GetCityForecastByZIPResult.City;

            foreach (Forecast day in results.Body.GetCityForecastByZIPResponse.GetCityForecastByZIPResult.ForecastResult.Forecast){
                Day x = new Day();
                x.daytime_high = day.Temperatures.DaytimeHigh;
                x.morning_low = day.Temperatures.MorningLow;
                content.data.days.Add(x);
            }

            return JsonConvert.SerializeObject(content);

This just returns a JSON array of days which is not what I want. I have the DateTime in my results object.

Upvotes: 1

Views: 7685

Answers (3)

Alex Skalozub
Alex Skalozub

Reputation: 2576

If you really want such a sophisticated format, I'd go with a custom JsonConverter:

public class Day
{
    public string morning_low { get; set; }
    public string daytime_high { get; set; }
}

[JsonConverter(typeof(Data.Converter))]
public class Data
{
    public string city_name { get; set; }
    public Dictionary<DateTime, Day> days { get; set; }

    public class Converter : JsonConverter
    {
        public override bool CanConvert(Type type) { return type == typeof(Data); }
        public override object ReadJson(JsonReader reader, Type type, object value, JsonSerializer serializer) 
        {
            Data obj = new Data();
            obj.days = new Dictionary<DateTime, Day>();
            DateTime v;
            while (reader.Read() && reader.TokenType != JsonToken.EndObject)
            {
                if (reader.TokenType != JsonToken.PropertyName)
                    throw new JsonSerializationException("Unexpected token type");

                if ("city_name" == (string)reader.Value)
                {
                    if (obj.city_name != null)
                        throw new JsonSerializationException("Duplicate key: city_name");
                    obj.city_name = reader.ReadAsString();
                }
                else if (DateTime.TryParseExact((string)reader.Value, serializer.DateFormatString, 
                                                serializer.Culture, DateTimeStyles.None, out v))
                {
                    reader.Read();
                    obj.days.Add(v, serializer.Deserialize<Day>(reader));
                }
                else
                {
                    if (serializer.MissingMemberHandling == MissingMemberHandling.Error)
                        throw new JsonSerializationException("Unexpected property: " + reader.Value);
                    reader.Read();
                    serializer.Deserialize(reader, reader.ValueType);
                }
            }
            return obj;
        }
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Data obj = (Data)value;
            writer.WriteStartObject();
            writer.WritePropertyName("city_name");
            writer.WriteValue(obj.city_name);
            foreach (var pair in obj.days)
            {
                writer.WritePropertyName(pair.Key.ToString(serializer.DateFormatString));
                serializer.Serialize(writer, pair.Value);
            }
            writer.WriteEndObject();
        }
    }
}

public class ResultContent
{
    public Data data { get; set; }
}

public class ResultContentRoot
{
    public ResultContent result_content { get; set; }
}

public static void Main()
{
    var data = new Data();
    data.city_name = "New York";
    data.days = new Dictionary<DateTime, Day>();
    data.days.Add(DateTime.Today, new Day() { morning_low = "24", daytime_high = "29" });

    var result_content = new ResultContent();
    result_content.data = data;

    var root = new ResultContentRoot();
    root.result_content = result_content;

    var s = JsonConvert.SerializeObject(root);
}

I think it is the only way to mix dictionary and object contracts.

If you only need one-way serialization, you may also go with dynamic. It takes less code:

public class Day
{
    public string morning_low { get; set; }
    public string daytime_high { get; set; }
}

public class ResultContent
{
    public dynamic data { get; set; }
}

public class ResultContentRoot
{
    public ResultContent result_content { get; set; }
}

public static void Main()
{
    dynamic data = new ExpandoObject();
    data.city_name = "New York";
    IDictionary<string, object> days = (IDictionary<string, object>)data;
    days.Add(DateTime.Today.ToString("yyyy-MM-dd'T'HH:mm:ss"), new Day() { morning_low = "24", daytime_high = "29" });

    var result_content = new ResultContent();
    result_content.data = data;

    var root = new ResultContentRoot();
    root.result_content = result_content;

    var s = JsonConvert.SerializeObject(root);
}

But it is very close to discard all that strong typing and just construct response with JObjects.

Upvotes: 1

Aydin
Aydin

Reputation: 15314

This is probably what you're looking for in that case...

void Main()
{
    Result result = new Result 
    {
        Data = new Data 
        {
            WeatherData = new List<City> 
            {
                new City 
                {
                    Name = "London",
                    Temp = new Dictionary<DateTime, TemperatureRange> 
                    {
                        { 
                            DateTime.UtcNow, 
                            new TemperatureRange 
                            {
                                DayHigh = 0,
                                MorningLow = 50
                            }
                        }
                    }
                }
            }
        }       
    };

    JsonConvert.SerializeObject(result);
}

public class Result
{
    [JsonProperty("result_content")]
    public Data Data { get; set; }
}

public class Data 
{
    [JsonProperty("data")]
    public List<City> WeatherData { get; set; }
}

public class City
{
    [JsonProperty("city_name")]
    public string Name { get; set; }

    public Dictionary<DateTime, TemperatureRange>  Temp { get; set; }
}

public class TemperatureRange
{
    public int MorningLow { get; set; }
    public int DayHigh { get; set; }
}

Upvotes: 1

benji
benji

Reputation: 606

I think it should be an array of days and representing it the way you asked wouldn't be good, because creating a dynamic json format is difficult to parse.

They way you defined it should produce something like this below.

{
    "result_content": {
        "data": {
             "city_name" : "Beverly Hills",
             "days" : 
             [
                 {
                     "morning_low": "20",
                     "daytime_high": "40"
                 },
                 {
                     "morning_low": "21",
                     "daytime_high": "41"
                 },
                 {
                     "morning_low": "22",
                     "daytime_high": "42"
                 },
                 {
                     "morning_low": "23",
                     "daytime_high": "43"
                 },
                 {
                     "morning_low": "24",
                     "daytime_high": "44"
                 }
            ]
        }
    }
}

What you're missing is the day itself, which should be defined in your Day class. Add it to get:

"days" : 
[
    {
        "day" : "2014-06-05T00:00:00",
        "morning_low": "20",
        "daytime_high": "40"
    }
    ...
]

Upvotes: 1

Related Questions