6

I am running into a strange problem with serializing data with json.net. Mainly, I am trying to rename the 'Key' and 'Value' names in the outgoing json to be something more descriptive. Specifically, I want the IRequest related 'Key' to be called 'Request' and the IQuoteTimeSeries 'Value' to be 'DataSeries'

Note, this will not be deserialized. It is only used in data analysis on the web page.

The data repository object I am serializing is a Dictionary<IRequest, IQuoteTimeSeries> object. The IRequest represents a specific request for data and the IQuoteTimeSeries is the object that contains the returning data as a SortedDictionary<DateTime, IQuote>. This is a series of data sorted by timestamp. In this example, I only have one item in the TimeSeries for brevity, but in most cases there would be many items.

Everything needs to be organized together, serialized and sent out to be consumed by JavaScript.

Here is the basic code for these objects;

[JsonArray]
public class QuoteRepository : Dictionary<IRequest, IQuoteTimeSeries>, IQuoteRepository
{
    public QuoteRepository() { }   

    public void AddRequest(IRequest request)
    {
        if (!this.ContainsKey(request))
        {
            IQuoteTimeSeries tSeries = new QuoteTimeSeries(request);
            this.Add(request, tSeries);
        }
    }

    public void AddQuote(IRequest request, IQuote quote)
    {        
        if (!this.ContainsKey(request))
        {
            QuoteTimeSeries tSeries = new QuoteTimeSeries(request);
            this.Add(request, tSeries);
        }        
        this[request].AddQuote(quote);        
    }    

    IEnumerator<IQuoteTimeSeries>  Enumerable<IQuoteTimeSeries>.GetEnumerator()
    {
        return this.Values.GetEnumerator();
    }
}

A quote time series looks like this;

[JsonArray]
public class QuoteTimeSeries: SortedDictionary<DateTime, IQuote>, IQuoteTimeSeries
{  
    public QuoteTimeSeries(IRequest request)
    {
        Request = request;
    }
    public IRequest Request { get; }
    public void AddQuote(IQuote quote)
    {
        this[quote.QuoteTimeStamp] = quote;
    }

    public void MergeQuotes(IQuoteTimeSeries quotes)
    {
        foreach (IQuote item in quotes)
        {
            this[item.QuoteTimeStamp] = item;
        }
    }

    IEnumerator<IQuote> IEnumerable<IQuote>.GetEnumerator()
    {
        return this.Values.GetEnumerator();
    }

}

The code used to serialize this is fairly simple:

IQuoteRepository quotes = await requests.GetDataAsync();

JsonSerializerSettings settings = new JsonSerializerSettings
{
    ContractResolver = QuoteRepositoryContractResolver.Instance,
    NullValueHandling = NullValueHandling.Ignore
};

return Json<IQuoteRepository>(quotes, settings);

I added a contract resolver with the intention of overriding the property writing. The property.PropertyName = code is being hit and the property names are being changed, but the output JSON is unaffected.

public class QuoteRepositoryContractResolver : DefaultContractResolver
{
    public static readonly QuoteRepositoryContractResolver Instance = new QuoteRepositoryContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.DeclaringType == typeof(KeyValuePair<IRequest, IQuoteTimeSeries>))
        {
            if (property.PropertyName.Equals("Key", StringComparison.OrdinalIgnoreCase))
            {
                property.PropertyName = "Request";
            }
            else if (property.PropertyName.Equals("Value", StringComparison.OrdinalIgnoreCase))
            {
                property.PropertyName = "Data";
            }
        }
        else if (property.DeclaringType == typeof(KeyValuePair<DateTime, IQuote>))
        {
            if (property.PropertyName.Equals("Key", StringComparison.OrdinalIgnoreCase))
            {
                property.PropertyName = "TimeStamp";
            }
            else if (property.PropertyName.Equals("Value", StringComparison.OrdinalIgnoreCase))
            {
                property.PropertyName = "Quote";
            }
        }
        return property;
    }
}

The output JSON is odd. The Key and Value items are completely unchanged, even though I did change their names in the code.

[
    {
        "Key": {
            "QuoteDate": "2016-05-12T00:00:00-04:00",
            "QuoteType": "Index",
            "Symbol": "SPY",
            "UseCache": true
        },
        "Value": [
            {
                "Key": "2016-05-11T16:00:01-04:00",
                "Value": {
                    "Description": "SPDR S&amp;P 500",
                    "High": 208.54,
                    "Low": 206.50,
                    "Change": -1.95,
                    "ChangePer": -0.94,
                    "Price": 206.50,
                    "QuoteTimeStamp": "2016-05-11T16:00:01-04:00",
                    "Symbol": "SPY"
                }
            }
        ]
    },
    {
        "Key": {
            "QuoteDate": "2016-05-12T00:00:00-04:00",
            "QuoteType": "Stock",
            "Symbol": "GOOG",
            "UseCache": true
        },
        "Value": [
            {
                "Key": "2016-05-11T16:00:00-04:00",
                "Value": {
                    "Description": "Alphabet Inc.",
                    "High": 724.48,
                    "Low": 712.80,
                    "Change": -7.89,
                    "ChangePer": -1.09,
                    "Price": 715.29,
                    "QuoteTimeStamp": "2016-05-11T16:00:00-04:00",
                    "Symbol": "GOOG"
                }
            }
        ]
    }
]

Does anyone know how to change the 'Key' and 'Value' items properly​?

1
  • @Brian Rogers Thank you for the cleanup. My typing skills aren't always that great at 4:00 AM in the morning. :-) Commented May 12, 2016 at 16:27

2 Answers 2

5

One way to solve this is to use a custom JsonConverter for your dictionary-based classes. The code is actually pretty straightforward.

public class CustomDictionaryConverter<K, V> : JsonConverter
{
    private string KeyPropertyName { get; set; }
    private string ValuePropertyName { get; set; }

    public CustomDictionaryConverter(string keyPropertyName, string valuePropertyName)
    {
        KeyPropertyName = keyPropertyName;
        ValuePropertyName = valuePropertyName;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<K, V>).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IDictionary<K, V> dict = (IDictionary<K, V>)value;
        JArray array = new JArray();
        foreach (var kvp in dict)
        {
            JObject obj = new JObject();
            obj.Add(KeyPropertyName, JToken.FromObject(kvp.Key, serializer));
            obj.Add(ValuePropertyName, JToken.FromObject(kvp.Value, serializer));
            array.Add(obj);
        }
        array.WriteTo(writer);
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

When it is time to serialize, add the converters to the JsonSerializerSettings like this:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    Converters = new List<JsonConverter>
    {
        new CustomDictionaryConverter<IRequest, IQuoteTimeSeries>("Request", "Data"),
        new CustomDictionaryConverter<DateTime, IQuote>("TimeStamp", "Quote")
    },
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(repo, settings);

Fiddle: https://dotnetfiddle.net/roHEtx

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. I ran some tests and the code above worked exactly like I needed it to. I have been beating my head against a wall trying to figure out how to modify those values. Your answer was perfect.
Glad I could help.
-1

Converting your dictionary from Dictionary<IRequest, IQuoteTimeSeries> to List<KeyValuePair<IRequest, IQuoteTimeSeries>> should also work.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.