4

I have been working on a project which needs to save and load data through a JSON file. This JSON file includes various lists of other objects. When I go ahead and deserialize the file, however, this happens:

System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported.

The code that takes care of deserializing is as follows:

        public void LoadFromJson() {

        int userCount = File.ReadAllLines(folder + "/users").Length;
        int shopCount = File.ReadAllLines(folder + "/shops").Length;
        using (FileStream fileUsers = File.Open(folder + "/users", FileMode.Open, FileAccess.Read)) {
            StreamReader srUser = new StreamReader(fileUsers);
            for(int i=0; i<userCount; i++){
                ListOfUsers.Add(JsonSerializer.Deserialize<User>(srUser.ReadLine()));
            }
            srUser.Close();
            fileUsers.Close();
        }  

        using (FileStream fileShops = File.Open(folder + "/shops", FileMode.Open, FileAccess.Read)){
            StreamReader srShops = new StreamReader(fileShops);
            for(int i=0; i<shopCount; i++){
                ListOfShops.Add(JsonSerializer.Deserialize<Shop>(srShops.ReadLine()));
            }
            srShops.Close();
            fileShops.Close();
        }       

    }

Classes I'm trying to deserialize

    public abstract class Shop{

    public List<Sellable> ShopList { get; set; }
    public int TotalValue { get; set; }
    public string shopName { get; set; }

    public Shop(List<Sellable> list, string shopname){
        ShopList = list;
        shopName = shopname;
    }

    public abstract bool AddToShop(Sellable item);
    public abstract bool RemoveFromShop(string item);
    public abstract int GetValue(string name);
    public abstract string PrintShop();

}

    public abstract class Furniture : Sellable{
    public int Space { get; set; }
    public Conditions Condition { get; set; }
    public Materials Material { get; set; }

    [JsonConstructorAttribute]
    public Furniture(int val, int spc, string nm, Conditions condition, Materials material) : base (val, nm){
        Space = spc;
        Condition = condition;
        Material = material;
    }

}

    public abstract class Sellable{
    public int Value { get; set; }
    public string Name { get; set; }

    [JsonConstructorAttribute]
    public Sellable(int val, string name){
        Value = val;
        Name = name;
    }

JsonConverter

    public class SellableConverter : JsonConverter<Sellable>{ 

    public enum Type{
        Sellable,
        Furniture,
        Wearable,

    }

    public override Sellable Read(ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException();

        if (!reader.Read()
                || reader.TokenType != JsonTokenType.PropertyName
                || reader.GetString() != "Type") throw new JsonException();

        if (!reader.Read() || reader.TokenType != JsonTokenType.Number) throw new JsonException();

        Sellable baseClass;
        Type typeDiscriminator = (Type)reader.GetInt32();
        switch (typeDiscriminator)
        {
            case Type.Furniture:
                if (!reader.Read() || reader.GetString() != "TypeValue") throw new JsonException();
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) throw new JsonException();
                baseClass = (Furniture.Furniture)JsonSerializer.Deserialize(ref reader, typeof(Furniture.Furniture), options);
                break;
            case Type.Wearable:
                if (!reader.Read() || reader.GetString() != "TypeValue") throw new JsonException();
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) throw new JsonException();
                baseClass = (Wearable)JsonSerializer.Deserialize(ref reader, typeof(Wearable), options);
                break;
            case Type.Sellable:
                if (!reader.Read() || reader.GetString() != "TypeValue") throw new JsonException();
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject) throw new JsonException();
                baseClass = (Sellable)JsonSerializer.Deserialize(ref reader, typeof(Sellable));
                break;
            default:
                throw new NotSupportedException();
        }

        if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject) throw new JsonException();

        return baseClass;
    }

    public override void Write(Utf8JsonWriter writer, Sellable value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        if (value is Furniture.Furniture derivedA)
        {
            writer.WriteNumber("TypeDiscriminator", (int)Type.Furniture);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, derivedA, options);
        }
        else if (value is Wearable derivedB)
        {
            writer.WriteNumber("TypeDiscriminator", (int)Type.Wearable);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, derivedB, options);
        }
        else if (value is Sellable baseClass)
        {
            writer.WriteNumber("TypeDiscriminator", (int)Type.Sellable);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, baseClass);
        }
        else throw new NotSupportedException();

        writer.WriteEndObject();
    }
}

SaveToJson method:

        public void SaveToJson(){

        FileStream fileUsers;
        FileStream fileShops;

        if(!(File.Exists(folder + "/users"))) fileUsers = File.Create(folder + "/users");
        else fileUsers = File.OpenWrite(folder + "/users");
        if(!(File.Exists(folder + "/shops"))) fileShops = File.Create(folder + "/shops");
        else fileShops = File.OpenWrite(folder + "/shops");

        StreamWriter srUser = new StreamWriter(fileUsers);
        StreamWriter srShop = new StreamWriter(fileShops);

        var serializeOptions = new JsonSerializerOptions();
        serializeOptions.Converters.Add(new SellableConverter());

        for(int i=0; i<ListOfUsers.Count; i++){
            srUser.WriteLine(JsonSerializer.Serialize<User>(ListOfUsers[i]), serializeOptions);
            Console.WriteLine("Debug: " + "\n" + "Object: " + ListOfUsers[i] + "\n" + "Json: " + JsonSerializer.Serialize<User>(ListOfUsers[i]));
        }
        for(int i=0; i<ListOfShops.Count; i++){
            srShop.WriteLine(JsonSerializer.Serialize<Shop>(ListOfShops[i]), serializeOptions);
            Console.WriteLine("Debug: " + "\n" + "Object: " + ListOfShops[i] + "\n" + "Json: " + JsonSerializer.Serialize<Shop>(ListOfShops[i]));
        }

        srUser.Close();
        fileUsers.Close();
        srShop.Close();
        fileShops.Close();

    }

How can I fix it? Thanks in advance! :)

EDIT: I added the classes I'm trying to deserialize. I apologize if I'm not giving enough detail or I make dumb mistakes, I am still a student and trying to mess with most of these things for the first time

5
  • The problem with your question is that, you post the error but don't post the relevant code. Commented Jan 30, 2021 at 2:25
  • Please share a minimal reproducible example, specifically the type Shop you are trying to deserialize. But see How to use immutable types and non-public accessors with System.Text.Json for how to deal with types without a public parameterless constructor in general. Commented Jan 30, 2021 at 2:45
  • I hope I added enough info. I'm sorry if I made mistakes in specifying the code needed to understand the issue, but I'm not very knowledgeable on the matter yet! I apologize again Commented Jan 30, 2021 at 3:03
  • This question is > 3y old, and the answers have somewhat evolved. You 'll get this message when you actually do use a type with a parameter in the constructor, and than consult answers below, but also if you want to convert to a an abstract base class (polymorphic deserialisation) with a parameterless constructor. The following topic will provide with the appropriate guidance stackoverflow.com/questions/58074304/…. Commented May 11, 2024 at 6:07
  • I encountered this while serializing a dataSetTable. To fix it, i just used Newtwonsoft.JSON.JsonConvert.Serialize(). Seems to get around the issue entirely. Commented Sep 5, 2024 at 19:14

3 Answers 3

8

Your exception says that you can't deserialize classes that don't have a default (parameterless) constructor.

The class you are deserializing, or one of the classes included as a property of that class, has a constructor that takes arguments, and doesn't also have a default constructor.

The deserializer isn't able to create an instance of that class, since it doesn't have the parameters to pass to that constructor.

Without seeing the definition of the classes you are trying to deserialize, I'm not able to help any further.

EDIT: It looks like Newtonsoft.Json was good enough to give some attributes, which you are ALMOST using correctly, to get around this problem. The JsonConstructorAttribute will match up properties from the serialized string being deserialized with constructor parameters, provided the names match (ignoring case).

[JsonConstructor]
public Sellable(int value, string name){
    Value = value;
    Name = name;
}

Thanks to Brian Rogers' answer for the clue I needed to find the relevant documentation!

Sign up to request clarification or add additional context in comments.

7 Comments

I added the classes I'm trying to serialize. I hope it's enough, as stated in the edit, I'm a student and I am not very knowledgeable of the matter (JSON and serialization/deserialization), and I rarely ever used forums to ask for help. Sorry for the inconvenience!
The issue is that all 3 of your classes only define constructors that take parameters and so the serializer can't create instances of those classes. If you add another constructor to each of your classes that take no parameters, your serializer will be able to deserialize those classes. We were all new once; you'll get there! :-)
I see, but wouldn't using a parameterless constructor fail to correctly insert values into the variables?
I've never used this before, but there might be a way based on this other SO answer: stackoverflow.com/a/23017892/1429354. Basically, add [JsonConstructor] immediately above the constructors and it will try to match property names from the JSON to constructor parameter names. I learned something new today! :-)
I see you're already using [JsonConstructor] (the word Attribute is optional). You need to make sure the parameter names match the property names. So list needs to become shopList, val becomes value, etc.
|
0

The error says exactly what is wrong and how to fix it.

The target classes of deserializations (User and Shop) needs:

  • a parameterless constructor (IMHO the easiest, and most straightforward way);
  • a singular parameterized constructor; OR
  • a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported.

Alternatively you could deserialize to an dynamic or object and map the properties (See Deserialize JSON into C# dynamic object? for a small sample)

Also, and this REALLY bugs me, why are you reading the file and then reading each line? You should do something like

using(var fileUsers = File.Open(folder + "/users", FileMode.Open, FileAccess.Read))
using(var srUser = new StreamReader(fileUsers))
{
    string line;
    while((line = srUser.ReadLine()) != null)
    {
        ListOfUsers.Add(JsonSerializer.Deserialize<User>(line));
    }
}

9 Comments

The reason mainly is that I'm still learning most of this stuff. I obviously look around but I'm a student, and whilst i know the concepts and some syntax, I still need to learn a lot to optimize the code. As per the parameterless constructor, wouldn't it fail to then add the various values to the variables?
That is alright. The deserializer does exactly that. It tries to automatically match the properties of the class with the names provided on the json, but first it must create an instance of the class (using a constructor). I see you updated your question, but the target class can't be abstract (meaning you can't instantiate it).
However, what if I must have an abstraction? Isn't there a way to instantiate a child object in place? For example, Furniture, being a class that extends Sellable. So it would add to the List<Sellable> an object of type Furniture
Yeah, you can deserialize a child object and add it to a collection of a parent, no problem there. But the class being deserialized can't be abstract, since you can't instantiate one. In you example, Furniture is also abstract.
I turned Furniture into a regular class, the issue is "Sellable". Since shop has a List<Sellable> I get the same error now with Sellable, since it's an abstract class. I don't know how to solve the issue
|
0

I got the a similar 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 Sellable(){

}

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

public Sellable() : this(0, "")
{
}

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.