35

I have this API

 public ActionResult AddDocument([FromBody]AddDocumentRequestModel documentRequestModel)
        {
            AddDocumentStatus documentState = _documentService.AddDocument(documentRequestModel, DocumentType.OutgoingPosShipment);
            if (documentState.IsSuccess)
                return Ok();

            return BadRequest();
        }

And this is my request model

    public class AddDocumentRequestModel
    {
        public AddDocumentRequestModel(int partnerId, List<ProductRequestModel> products)
        {
            PartnerId = partnerId;
            Products = products;
        }

        [Range(1, int.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
        public int PartnerId { get; private set; }

        [Required, MustHaveOneElement(ErrorMessage = "At least one product is required")]
        public List<ProductRequestModel> Products { get; private set; }
    }

so when I'm trying to hit the API with this body

{
        "partnerId": 101,
        "products": [{
            "productId": 100,
            "unitOfMeasureId": 102,
            "quantity":5
        }
     ]
}

this is the request:

System.NotSupportedException: Deserialization of reference types without parameterless constructor is not supported. Type 'Alati.Commerce.Sync.Api.Controllers.AddDocumentRequestModel'

I don't need a parameterless constructor, because it doesn't read the body parameters. Is there any other way for deserialization?

Dale K
  • 25,246
  • 15
  • 42
  • 71
Yoana Gancheva
  • 529
  • 1
  • 5
  • 10
  • 2
    Some IDEs (such as Rider) will suggest you to turn a class used for serialization into abstract. Remove the abstract keyword (use concrete classes instead) and it will solve many other nuisances of this exception than OP mentioned. – tortal Oct 01 '20 at 13:38
  • 1
    Should work since .NET 5.0. See https://github.com/dotnet/runtime/issues/41313 – Palec Oct 13 '20 at 10:23
  • In .NET 6, we still need to resort to Newtonsoft.Json when dealing with System.Uri in dictionary keys. https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-supported-collection-types?pivots=dotnet-6-0#supported-key-types – Palec Dec 15 '21 at 11:44
  • @tortal your reply fixed the issue for me. – Abayomi Olowu Aug 22 '23 at 16:31

8 Answers8

39

You can achieve your desired result. You need to switch to NewtonsoftJson serialization (from package Microsoft.AspNetCore.Mvc.NewtonsoftJson)

Call this in Startup.cs in the ConfigureServices method:

services.AddControllers().AddNewtonsoftJson();

After this, your constructor will be called by deserialization.

Extra info: I am using ASP Net Core 3.1

Later Edit: I wanted to give more info on this, as it seems that this can also be achieved by using System.Text.Json, although custom implementation is necessary. The answer from jawa states that Deserializing to immutable classes and structs can be achieved with System.Text.Json, by creating a custom converter (inherit from JsonConverter) and registering it to the converters collection (JsonSerializerOptions.Converters) like so:

public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
{
...
}

and then...

var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new ImmutablePointConverter());
serializeOptions.WriteIndented = true;
Pang
  • 9,564
  • 146
  • 81
  • 122
Adrian Nasui
  • 1,054
  • 9
  • 10
  • 2
    Just make sure you are installing the correct version of Microsoft.AspNetCore.Mvc.NewtonsoftJson for the .Net or .Net Core framework you are using. It doesn't work to try installing the 5.x version for .Net Core 3.1, but installing the 3.1.x version does. {facepalm} – computercarguy Jun 24 '21 at 20:37
  • 1
    In .NET 6, we still need to resort to this solution when dealing with System.Uri in dictionary keys. https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-supported-collection-types?pivots=dotnet-6-0#supported-key-types – Palec Dec 15 '21 at 11:40
19

Just in case someone have the same issue I had, I was using abstract class, once removed the abstract key word, it all worked just fine.

IBRA
  • 1,502
  • 3
  • 24
  • 56
  • 1
    Any function that were abstracted can be declared as **virtual** instead. And put a **throw new NotImplementedException();** for good measure in those abstract functions so you can catch any derived class that does not override it. – Pic Mickael Sep 28 '22 at 00:34
4

Just Add [JsonConstructor] before your constructor

like this

public class Person
{
    public string Name { get; set; }
    public int LuckyNumber { get; private set; }
    
    [JsonConstructor]
    public Person(int luckyNumber)
    {
        LuckyNumber = luckyNumber;
    }
    public Person() { }
}
Ali
  • 300
  • 4
  • 11
1

There are still some limitations using System.Text.Json - have a look here: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson Deserialization without parameterless constructor using a parameterized constructor is not supported yet (but it's on their plan). You can implement your custom JsonConverter (like in this example: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#deserialize-to-immutable-classes-and-structs) or - like Adrian Nasul above suggested: use Newtonsoft.Json and then you can use the [JsonConstructor] attribute

jawa
  • 206
  • 2
  • 10
  • This is an intentional limitation they introduced with .Net 5. I assume it will never be fixed, essentially making `System.Text.Json` forever the `just doesn't work` partner of `it just works` Newtonsoft.Json. https://learn.microsoft.com/en-us/dotnet/core/compatibility/serialization/5.0/non-public-parameterless-constructors-not-used-for-deserialization – Douglas Gaskell Sep 15 '22 at 00:07
1

In my case I had set a class as internal and when I made it public it worked. The error message was really of little help with this specific circumstance.

Old (actual class name changed to ClassName in the example

internal class Rootobject
{
    [JsonConstructor]
    public Rootobject(ClassName className)
    {
        ClassName = className?? throw new ArgumentNullException(nameof(className));
    }

    public ClassName ClassName { get; set; }
}

New:

public class Rootobject
{
    [JsonConstructor]
    public Rootobject(ClassName className)
    {
        ClassName = branding ?? throw new ArgumentNullException(nameof(className));
    }

    public ClassName ClassName { get; set; }
}
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
0

In my case error, caused was inside InnerException. There is my class had a field with a custom class type that did not have a parameterless constructor. I've added a parameterless constructor to the inner class and the problem has gone away.

0

Just removed the constructor of my class. Resolved for me.

Alex SSantos
  • 13
  • 1
  • 8
0

I got the following runtime error using Blazor WASM (Blazor WebAssembly) and System.Text.Json:

fail: MyProject.Client.Shared.Error[0] Error:ProcessError - Type: System.NotSupportedException Message: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'MyProject.Shared.Models.DTO.MyDto'. Path: $[0].myDtos[0] | LineNumber: 0 | BytePositionInLine: 406. Exception: System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'MyProject.Shared.Models.DTO.MyDto'. Path: $[0].myDtos[0] | LineNumber: 0 | BytePositionInLine: 406. ---> System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported. Type 'MyProject.Shared.Models.DTO.MyDto'.

Using you model the easiest fix in my world is simply adding a parameterless constructor:

public class AddDocumentRequestModel
{
    public AddDocumentRequestModel(int partnerId, List<ProductRequestModel> products)
    {
        PartnerId = partnerId;
        Products = products;
    }

    public AddDocumentRequestModel()
    {
    }

    [Range(1, int.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int PartnerId { get; private set; }

    [Required, MustHaveOneElement(ErrorMessage = "At least one product is required")]
    public List<ProductRequestModel> Products { get; private set; }
}

If you need default values use constructor chaining which also works:

public AddDocumentRequestModel() : this(1, new List<ProductRequestModel>() { new ProductRequestModel()})
{
}

https://stackoverflow.com/a/1814965/3850405

Ogglas
  • 62,132
  • 37
  • 328
  • 418