Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change Name of 'Key' and 'Value' when using JSON.NET to serialize complex Dictionary

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​?

like image 249
drobertson Avatar asked May 12 '16 08:05

drobertson


People also ask

Does JSON serialize private fields?

All fields, both public and private, are serialized and properties are ignored.

How do you serialize an array in JSON?

To serialize a collection - a generic list, array, dictionary, or your own custom collection - simply call the serializer with the object you want to get JSON for. Json.NET will serialize the collection and all of the values it contains.

Can JSON be serialized?

Json namespace provides functionality for serializing to and deserializing from JavaScript Object Notation (JSON). Serialization is the process of converting the state of an object, that is, the values of its properties, into a form that can be stored or transmitted.

What is JSON data serialization?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).


1 Answers

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

like image 200
Brian Rogers Avatar answered Nov 02 '22 23:11

Brian Rogers