Search code examples
c#json.netjson-deserialization

How to deserialize number to enum value with Newtonsoft


In our .Net 5 web app, we have a custom model binder shown below. I've just run into an instance where it breaks. My model has a List property of PhoneNumbers. Each PhoneNumber has a PhoneType enum. This is where the deserializing error occurs. The error is Unable to cast object of type 'System.Double' to type 'PhoneType'.

public class JsonReferenceTypeModelBinder : IModelBinder
{
    private readonly Type _modelType;

    public JsonReferenceTypeModelBinder(Type modelType)
    {
        _modelType = modelType;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        object item = null;

        var serializer = new JsonSerializer
        {
            NullValueHandling = NullValueHandling.Ignore
        };
        serializer.Converters.Add(new DecimalSafeNumericJsonConverter());

        var json = Encoding.UTF8.GetString(await bindingContext.HttpContext.Request.Body.GetBytesAsync());

        try
        {
            using var textReader = new StringReader(json);
            using var reader = new JsonTextReader(textReader);
            item = serializer.Deserialize(reader, _modelType);
        }
        catch (JsonReaderException jsonEx)
        {
            // in case the body was url-encoded instead of serialized as json
            // (use case: ajax controller actions utilizing [ValidateAntiForgeryToken] appear to not allow requests formatted as json)
            var keyValues = new Dictionary<string, string>();
            var props = json.Split('&');
            foreach (var prop in props)
            {
                var keyAndValue = prop.Split('=');
                var decodedValue = HttpUtility.UrlDecode(keyAndValue[1]);
                keyValues.Add(keyAndValue[0], decodedValue);
            }

            json = JsonConvert.SerializeObject(keyValues);

            using var textReader = new StringReader(json);
            using var reader = new JsonTextReader(textReader);
            item = serializer.Deserialize(reader, _modelType);
        }
        catch (Exception ex)
        {
            return;
        }

        if (item != null)
            bindingContext.Result = ModelBindingResult.Success(item);
    }
}

Contact class

public class CompanyContact
{
    private string _email = string.Empty;
    private string _firstName = string.Empty;

    private string _lastName = string.Empty;

    private string _title = string.Empty;
    private List<Enums.ContactType> _contactType { get; set; } = new List<Enums.ContactType>();

    private List<Phone> _phoneNumbers { get; set; } = new List<Phone>();
    public int CompanyId { get; set; }

    public string id { get; set; } = string.Empty;
    
    public string FirstName
    {
        get => _firstName ?? string.Empty;
        set => _firstName = value;
    }

    public List<Email> Emails { get; set; } = new List<Email>();
    public Address Address { get; set; } = new Address();
    
    public string LastName
    {
        get => _lastName ?? string.Empty;
        set => _lastName = value;
    }

    public string Email
    {
        get => _email ?? string.Empty;
        set => _email = value;
    }
    
    public string PhoneNumber { get; set; } = string.Empty;
    
    public List<Enums.ContactType> ContactType
    {
        get => _contactType;
        set => _contactType = value;
    }
    
    public List<Phone> PhoneNumbers
    {
        get => _phoneNumbers;
        set => _phoneNumbers = value;
    }
    
    public string Title
    {
        get => _title ?? string.Empty;
        set => _title = value;
    }
}

Phone class

public class Phone
{
    private string _number = string.Empty;
    public Enums.PhoneType PhoneType { get; set; }

    public string Number
    {
        get => _number ?? string.Empty;
        set => _number = value;
    }
}

PhoneType enum

public enum PhoneType
{
    Home = 1, Work, Cellular, Pager, Fax, WorkFax, Alternate
}

Example json

{
    "FirstName": "Jim",
    "LastName": "Miller",
    "Title": "President",
    "PhoneNumbers":
    [
        {
            "Number": "123-123-1234",
            "PhoneType": 2
        }
    ],
    "Emails":
    [
        {
            "Address": "[email protected]",
            "EmailType": 2
        }
    ]
}

Solution

  • i am re-writing answer. option 1:

    [JsonConverter(typeof(StringEnumConverter))]
    public enum PhoneType ...
    
    

    and in enum

    [EnumMember(Value = "2")]
        Work,
    

    option 2 JsonConvert.DeserializeObject(json, new Newtonsoft.Json.Converters.StringEnumConverter() i will place a full example for you to see

    void Main()
    {
        string json = "{\"FirstName\":\"Jim\",\"LastName\":\"Miller\",\"Title\":\"President\",\"PhoneNumbers\":[{\"Number\":\"123-123-1234\",\"PhoneType\":2}],\"Emails\":[{\"Address\":\"[email protected]\",\"EmailType\":2}]}";
    
        Console.Write((JsonConvert.DeserializeObject<JSON>(json, new Newtonsoft.Json.Converters.StringEnumConverter() )));
    
    }
    //CLASS
    public class Email
    {
        public string Address { get; set; }
        public int EmailType { get; set; }
    }
    
    public class PhoneNumber
    {
        public string Number { get; set; }
        //[JsonConverter(typeof(StringEnumConverter))]
        public PhoneType PhoneType { get; set; }
    }
    
    public class JSON
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Title { get; set; }
        public List<PhoneNumber> PhoneNumbers { get; set; }
        public List<Email> Emails { get; set; }
    }
    
    //ENUM
    public enum PhoneType
    {
        Home = 1, Work, Cellular, Pager, Fax, WorkFax, Alternate
    }
    

    enter image description here