2

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.

7
  • 2
    You shouldn't normally use struct... Unless you have particular problems, you should use class. And try not to use Count() when you can use Length Commented May 22, 2015 at 10:17
  • 1
    Structs always have a public parameterless constructor which is why Json.Net is able to deserialize the struct at all. Why do you expect the deserializer to call your methods though, and why do you need them? The Level properties should already be serialized Commented May 22, 2015 at 10:24
  • See stackoverflow.com/a/7010231/613130 There is a limitation with Json.NET and Dictionary keys Commented May 22, 2015 at 10:31
  • @PanagiotisKanavos I think that perhaps he wants to use a custom formatter for serialization... To serialize it as Level1~Level2~Level3 instead of serializing the properties. Commented May 22, 2015 at 10:32
  • I should probably add that the dictionary is a property of another class, if that makes any diffrence Commented May 22, 2015 at 10:46

1 Answer 1

1

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

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

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.