Resolved Deserialization With Interfaces Issue

madaxe2020

Well-known member
Joined
Sep 7, 2020
Messages
50
Programming Experience
5-10
Were building a bunch of REST API End Points, we decided that we wanted a generic transportation object to be returned from all Rest calls that contains the data and other attributes.

The problem is that all of the models implement a common interface "IModlesBase" to support this. Now I don't know how to Deserialize since the child of the generic return object is an interface.

I know I have to tell the NewtonSoft what the concrete type is but I don't know how.

can anybody please show me how to get around this?

thanks

madaxe

C#:
GenericReturnModel claimTerms = await Environments.Get<GenericReturnModel>("/Url/v1")

Rest Call:
public static async Task<T> Get<T>(string url)
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(apiBasicUri);
                var result = await client.GetAsync(url);
                result.EnsureSuccessStatusCode();
                string resultContentString = await result.Content.ReadAsStringAsync();
                T resultContent = JsonConvert.DeserializeObject<T>(resultContentString);
                return resultContent;
            }
        }

GenericReturnModel:
public class GenericReturnModel
{
    public IModelsBase? ReturnModel { get; set; } = null;
}

IModelsBase:
    public interface IModelsBase
    {
    }
    public class ModelsBase : IModelsBase
    {
    }

C#:
public class EnvironmentModel : IModelsBase{}

public class EnvironmentVariableModel : IModelBase{}
 
Solution
There you go, here is my solution., and it works and I can have an extensible return type.


C#:
public class ConcreteConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="objectType"></param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns></returns>
        public override object? ReadJson(JsonReader reader, Type? objectType, object? existingValue, JsonSerializer? serializer)
        {
            JObject? jObject = JObject.Load(reader)...
Doing some reading i think i have to implement a concrete Convertor, I've done some testing and it seems if i can determine the type then i can change "return serializer.Deserialize<T>(reader);" in the Deserialize to convert tot he correct type. this is where I'm stuck.

thanks
mdaxe


C#:
        [JsonConverter(typeof(ConcreteConverter<ModelsBase>))]
        public IModelsBase? ReturnModel { get; set; } = null;

C#:
public override object ReadJson(JsonReader reader,
         Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jObject = JObject.Load(reader);
            if(objectType.Name == "IModelsBase")
            {
                //I dont know what im doing
            }
            return serializer.Deserialize<T>(reader);
        }
 
I have found a solution but i cant quite get it to work

if i decorate the class as follows

C#:
[JsonConverter(typeof(AbstractConverter<EnvironmentsModel,IModelsBase>))]
        public IModelsBase? ReturnModel { get; set; } = null;

it works great but that's only for one possible concrete type, so o found that you could define the settings for json and pass in multiple convertors this is failing but i dont know why

C#:
        public static async Task<T> Get<T>(string url)
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(apiBasicUri);
                var result = await client.GetAsync(url);
                result.EnsureSuccessStatusCode();
                string resultContentString = await result.Content.ReadAsStringAsync();

                var settings = new JsonSerializerSettings
                {
                    Converters =
                    {
                        new AbstractConverter<EnvironmentsModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentVariablesModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentVariableValuesModel,IModelsBase>()
                    },
                };

                T resultContent = JsonConvert.DeserializeObject<T>(resultContentString,settings);
                return resultContent;
            }
        }

C#:
public class AbstractConverter<TReal, TAbstract> : JsonConverter where TReal : TAbstract
    {
        public override bool CanConvert(Type objectType)
        => objectType == typeof(TAbstract);

        public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer jser)
        => jser.Deserialize<TReal>(reader);

        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer jser)
        => jser.Serialize(writer, value);
    }
 
figured it out, i had the decorator in my last built package so it was ignoring the convertors

C#:
var settings = new JsonSerializerSettings
                {
                    Converters =
                    {
                        new AbstractConverter<EnvironmentsModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentVariablesModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentVariableValuesModel,IModelsBase>()
                    },
                };
 
If I have one of the convertors it works fine but when there are multiple it only uses the first convertor. Is there a way to allow it to use multiple convertors?


C#:
var settings = new JsonSerializerSettings
                {
                    Converters =
                    {
                        new AbstractConverter<EnvironmentsModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentVariablesModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentVariableValuesModel,IModelsBase>(),
                        new AbstractConverter<EnvironmentFilesModel, IModelsBase>()
                    },
                };
 
There you go, here is my solution., and it works and I can have an extensible return type.


C#:
public class ConcreteConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="objectType"></param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns></returns>
        public override object? ReadJson(JsonReader reader, Type? objectType, object? existingValue, JsonSerializer? serializer)
        {
            JObject? jObject = JObject.Load(reader);
            JProperty? parentProp = (JProperty?)jObject?.First;
            switch (parentProp?.Name)
                {
                case "environmentModels":
                    return jObject?.ToObject<EnvironmentsModel>();
                case "environmentVariableModels":
                    return jObject?.ToObject<EnvironmentVariablesModel>();
                case "environmentVariableValueModels":
                    return jObject?.ToObject<EnvironmentVariableValuesModel>();
                case "environmentFileModels":
                    return jObject?.ToObject<EnvironmentFilesModel>();
                default:
                    return null;
            }
        }

        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer? serializer)
        {
            throw new NotImplementedException();
        }
    }
 
Solution
I agree with @cjard . It looks like you are solving a problem that you created for yourself by designing things down to be too generic, if you'll forgive the pun. You do not show how you currently use your GenericReturnModel, but I have a suspicion that you are doing lots of branching or downcasts downstream of the Get<GenericReturnModel>() .
 
Back
Top Bottom