IdentityServer4 has a strange way of returning roles. If there is only one role, the property is a string:
{ "sub": "55568182-9273-4c6f-8d61-6555f7f02551", "role": "role001" }
But if there is more than one role, the property is an array of strings:
{ "sub": "55568182-9273-4c6f-8d61-6555f7f02551", "role": [ "role001", "role002" ] }
If the property is a string, and you try to serialize this into an IEnumerable<string>, you get the following error:
Could not cast or convert from System.String to System.Collections.Generic.IEnumerable`1[System.String].
JsonSerializationException: Error converting value “xxx” to type ‘System.Collections.Generic.IEnumerable`1[System.String]
Or
Could not cast or convert from System.String to System.String[].
JsonSerializationException: Error converting value “xxx” to type ‘System.String[]’
To serialize this into a model class, you will need to implement a JsonConverter, which is a custom converter that, in this case, checks if the property is one object or several before serializing.
STEP 1: IMPLEMENT THE CUSTOM CONVERTER
This JsonConverter checks the property before serializing to see if the property is an array or not. If not, it will output the single item as an array:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; namespace MyCode { internal class CustomArrayConverter<T> : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(List<T>)); } public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JToken token = JToken.Load(reader); if (token.Type == JTokenType.Array) return token.ToObject<List<T>>(); return new List<T> { token.ToObjec<T>() }; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } }
STEP 2: USE THE CUSTOM CONVERTER IN THE MODEL CLASS:
The custom converter is used as a property on the attribute:
using System; using System.Collections.Generic; using Newtonsoft.Json; namespace MyCode { [Serializable] public class UserInfoModel { [JsonProperty("sub")] public string ID { get; set; } [JsonProperty("role")] [JsonConverter(typeof(CustomArrayConverter<string>))] public IEnumerable<string> Roles { get; set; } } }
The CustomArrayConverter will work on any type.
MORE TO READ:
- Newtonsoft JsonConverter
- Json Arrays from W3CSchools