简体   繁体   中英

C# JSON Deserialization System.NotSupportedException

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

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!

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));
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM