Search code examples
c#json.netdeserializationjson-deserialization

Deserialize to existing instance of classes in C#


This can be applied to several problems where there is a list with a limited number of items like the list of cities, currencies, languages etc.

I am trying to find a way to serialize a class to its Id and then deserialize back to its complete description. For example, if we have a city structure like this:

public struct City
{
    public string Name;
    public Country Country;
}

[JsonObject(MemberSerialization.OptIn)]
public class Country
{
    public Country(string code, string name) { Code = code; Name = name; }
    [JsonProperty]
    public string Code;
    public string Name;
}

public class Countries
{
    public static List<Country> All = new List<Country>()
    {
        new Country("US", "United States"),
        new Country("GB", "United Kingdom"),
        new Country("FR", "France"),
        new Country("ES", "Spain"),
    };
}

I don't mind the Country serialized as {"code":"ES"} or simply "ES", but I would like it to be deserialized as the existing instance of country in Countries.All

How could I get this behavior?


Solution

  • I would recommend using a JsonConverter like so:

    public class CountryConverter : JsonConverter
    {
        public override bool CanRead { get { return true; } }
        public override bool CanWrite { get { return false; } }
    
        public override bool CanConvert(Type objectType)
        {
            return typeof(Country) == objectType;
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var obj = JObject.Load(reader);
            var code = obj.GetValue("code", StringComparison.OrdinalIgnoreCase)?.Value<string>();
            if (code != null)
            {
                return Countries.All.FirstOrDefault(c => string.Equals(c.Code, code, StringComparison.OrdinalIgnoreCase));
            }
            return null;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    It doesn't take the responsibility of serializing objects, only deserializing them. To deserialize them, it reads the "code" field, and then returns the first matching country from the Countries.All list. It would probably be best (more efficient) to make this use a dictionary instead.

    To use this, simply decorate your country class like so:

    [JsonConverter(typeof(CountryConverter))]
    public class Country