Reputation: 209
I have a c# struct that is used as dictionary key. In order to make that dictionary convert to json I need struct serialized to string (like json.net does for built in structs).
public struct CreditRating
{
public CreditRating(string json) : this()
{
var levels = json.Split(new[] { '~' }, StringSplitOptions.None);
if (levels.Count() >= 3) Level3 = levels[2];
if (levels.Count() >= 2) Level2 = levels[1];
if (levels.Any()) Level1 = levels[0];
}
public string Level1 { get; set; }
public string Level2 { get; set; }
public string Level3 { get; set; }
public override string ToString()
{
return string.Format("{0}~{1}~{2}", Level1, Level2, Level3);
}
public static CreditRating Parse(string json)
{
return new CreditRating(json);
}
}
And my test:
var rating = new CreditRating { Level1 = "first", Level2 = "Sergey" };
var ratingJson = JsonConvert.SerializeObject(rating); // {"Level1":"first","Level2":"Sergey","Level3":null}
var rating2 = JsonConvert.DeserializeObject<CreditRating>(ratingJson);
var dict = new Dictionary<CreditRating, double> {{rating, 2d}};
var dictJson = JsonConvert.SerializeObject(dict); //{"first~Sergey~":2.0}
var failingTest = JsonConvert.DeserializeObject<Dictionary<CreditRating, double>>(dictJson);
The last statement fails as it does not call into my Parse method or the public constructor. I followed the documentation but can't get pass this.
Upvotes: 2
Views: 9264
Reputation: 209
Ok, so after trying a lot of things this worked in the end - In case anyone else bumps into this:
[DataContract(Namespace = ContractNamespace.Current)]
public class CreditSpreadShiftWithLevels
{
[OnDeserializing]
private void Initialize(StreamingContext ctx)
{
ShiftsByRating = new Dictionary<CreditRating, double>();
}
[DataMember]
public bool SplitByRating { get; set; }
[DataMember]
public double ShiftValue { get; set; }
[DataMember]
[JsonConverter(typeof(CreditRatingDoubleDictionaryConverter))]
public Dictionary<CreditRating, double> ShiftsByRating { get; set; }
//other properties
}
[DataContract(Namespace = ContractNamespace.Current)]
public struct CreditRating
{
public CreditRating(string json): this()
{
var levels = json.Split(new[] { '~' }, StringSplitOptions.None);
var cnt = levels.Length;
if (cnt >= 3) Level3 = levels[2];
if (cnt >= 2) Level2 = levels[1];
if (cnt >= 1) Level1 = levels[0];
}
[DataMember]
public string Level1 { get; set; }
[DataMember]
public string Level2 { get; set; }
[DataMember]
public string Level3 { get; set; }
public override string ToString()
{
return string.Format("{0}~{1}~{2}", Level1, Level2, Level3);
}
}
public class CreditRatingDoubleDictionaryConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dict = new Dictionary<CreditRating, double>();
while (reader.Read())
{
if (reader.TokenType == JsonToken.PropertyName)
{
string readerValue = reader.Value.ToString();
var cr = new CreditRating(readerValue);
if (reader.Read() && reader.TokenType == JsonToken.Float)
{
var val = Convert.ToDouble(reader.Value);
dict.Add(cr, val);
}
}
if (reader.TokenType == JsonToken.EndObject) return dict;
}
return dict;
}
public override bool CanConvert(Type objectType)
{
return typeof(Dictionary<CreditRating, double>).IsAssignableFrom(objectType);
}
}
In short I created converter for the dictionary (and not struct) that was giving me grief and attributed property of the parent class. This made json.net call into my custom logic when deserializing. There is something already built into the library that makes dictionary serialization call into struct's ToString when creating dictionary key (this makes it slightly inconsistent as it does not respect the return route even though documentation sort of suggests it - http://docs.servicestack.net/text-serializers/json-serializer)
One pain point with this that I need to provide separate converter for every different type of dictionary that is using the struct for its key e.g.
public Dictionary<CreditRating, List<string>> BucketsByRating { get; set; }
will need another converter. I need to see if generics can be used to increase reuse but it would have been much better if I could provide single converter for struct that would be picked up for all different dictionary properties that I have.
In any case I hope this comes useful and saves someone some time.
Thanks to all who offered advice, much appriciated
Upvotes: 1