12

I am passing in a valid JSON object to my controller on my .net core 3 web api application. Doing so, I get the error:

System.NotSupportedException: Deserialization of interface types is not supported. Type 'OrderTranslationContracts.OrderContracts+IImportOrderLineModel'

So I looked in my code and I have the following concrete implementation of an Interface. Here is the line I think that's throwing the error:

   public List<OrderContracts.IImportOrderLineModel> Lines { get; set; }

Here is the portion of the JSON I am passing into the controller:

"lines": [
        {
            "orderNumber": "LV21131327",
            "lineNumber": 1,
            "itemId": "3083US",
            "customerItemId": "3083US",
            "quantity": 3,
            "price": 0.00,
            "quantityBackOrdered": null,
            "comments": "",
            "pickLocation": "",
            "orderFilled": "O",
            "hostUom": null,
            "type": null
        }

So I know the JSON is valid. Here is the signature for the controller:

[HttpPost]
    public async Task<List<ImportOrderModel>> Post([FromBody] List<ImportOrderModel> orders)
    {
        var response = await _validateOrder.ValidateAllOrdersAsync(orders, null);
        return response;
    }

I don't even break into this code as I am assuming that the JSON deserializer is throwing the error as it tries to convert it. So how do I over come this error? I am bound by the concrete implementation for the interface so I can't change the interface I need to work with what I have here if possible. If that isn't possible, are there any "work arounds" for this?

dbc
  • 104,963
  • 20
  • 228
  • 340
john
  • 1,273
  • 3
  • 16
  • 41

3 Answers3

5

I had the same problems with HttpClient.GetFromJsonAsync I tried httpClient.GetFromJsonAsync<ICustomer>(url);

And I got the error:

Deserialization of inteface types not supported using System.Text.JSON

From what I was able to find out, a model has to be available to deserialize an InterfaceType. My solution defines the model in the interface using data annotations.

  1. Create a TypeConverter (I found this class here: Casting interfaces for deserialization in JSON.NET)

    using Newtonsoft.Json;

    public class ConcreteTypeConverter<TConcrete> : JsonConverter
     {
         public override bool CanConvert(Type objectType)
         {
             //assume we can convert to anything for now
             return true;
         }
    
         public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
         {
             //explicitly specify the concrete type we want to create
             return serializer.Deserialize<TConcrete>(reader);
         }
    
         public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
         {
             //use the default serialization - it works fine
             serializer.Serialize(writer, value);
         }
     }
    

2 Add Data Annotations in your Interface ([JsonConverter(typeof(ConcreteTypeConverter<AddressModel>))])

using Newtonsoft.Json;
public interface ICustomer
{
    int Id { get; set; }
    int Name { get; set; }

    [JsonConverter(typeof(ConcreteTypeConverter<AddressModel>))]
    IAddress Address { get; set; }
}

3 Unfortunatly HttpClient.GetFromJsonAsync does not use Newtonsoft. I wrote my own method

public async static Task<T> GetJsonAsync<T>(HttpClient client, string url)
{
    using var response = await client.GetAsync(url);
    response.EnsureSuccessStatusCode();

    using Stream stream = await response.Content.ReadAsStreamAsync();
    using (var reader = new StreamReader(stream, Encoding.UTF8))
    {
        return JsonConvert.DeserializeObject<T>(reader.ReadToEnd(), new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Objects,
            NullValueHandling= NullValueHandling.Ignore
        });
    }
}

4 Now i can use:

 HttpClient httpClient= new HttpClient();
 string url="https://example.com/api/customerlist";
 var myCustomerList[]=await GetJsonAsync<CutomerModel[]>(httpClient, url);
Chris Berlin
  • 744
  • 6
  • 15
  • How does this work without annotations? What if I have an interface `IFoo` that has property `IEnumerable` ? – whobetter Mar 13 '22 at 14:38
  • I did not find solution without annotation for a Interface as property, because you need to have a model for the api-contract. The solution without annotation is, that you use IEnumerable instead of IEnumerable where Bar implements IBar. – Chris Berlin Mar 14 '22 at 18:51
-1

Following the answer given by @Chris Berlin, I tried the same but in my case, this problem was at the controller level- meaning while calling my API from the postman I was facing the issue (the action method parameter model class had interface properties).

I modified my startup.cs to use Newtonsoft.json instead of System.json by doing the following changes in my start.cs (along with @Chris Berlin's solution):

services.AddControllers().AddNewtonsoftJson();
services.AddControllersWithViews().AddNewtonsoftJson();   //optional
services.AddRazorPages().AddNewtonsoftJson();   //optional

and it worked.

-7

Here:

public List<OrderContracts.IImportOrderLineModel> Lines { get; set; }

Your list is of type IImportOrderLineModel Interface.

it should be like

 public List<ImportOrderLineModel> Lines { get; set; }

that ImportOrderLineModel is a class that implements it like:

public class ImportOrderLineModel : IImportOrderLineModel
{
    //......
}
Ashkan Mobayen Khiabani
  • 33,575
  • 33
  • 102
  • 171
  • 9
    OP should use a JsonConverter to map interface to concrete types. It defeats the purpose of interfaces (and violates SOLID), to use the concrete implementations in this way. –  Dec 05 '19 at 20:55
  • Thanks I put this in and it was able to get past the error. – john Dec 05 '19 at 20:55
  • This is just a workaround and doesn't solve the problem. Please use @user12447201 his solutions – Koenman Apr 07 '21 at 10:20