C# JSON反序列化 System.NotSupportedException

4

我一直在开发一个需要通过JSON文件保存和加载数据的项目。这个JSON文件包含其他对象的各种列表。然而,当我尝试反序列化文件时,会出现以下错误:

System.NotSupportedException: 不支持没有无参构造函数、只有一个有参数构造函数或用 'JsonConstructorAttribute' 注释的带参数构造函数的类型的反序列化。

负责反序列化的代码如下:

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

    }

我尝试反序列化的类

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

Json转换器

    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 方法:

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

    }

我该如何修复它?提前谢谢!:)

编辑:我添加了我正在尝试反序列化的类。如果我没有给出足够的细节或犯了愚蠢的错误,请见谅,我还是一名学生,并且第一次尝试处理大部分这些问题。


你的问题在于你只贴出了错误信息,却没有贴出相关代码。 - aybe
请提供一个 [mcve],特别是您正在尝试反序列化的类型 Shop。但是请参阅如何使用不可变类型和非公共访问器与System.Text.Json,以了解如何处理没有公共无参数构造函数的类型。 - dbc
我希望我添加了足够的信息。如果我在指定需要理解问题的代码方面犯了错误,我很抱歉,但我对此还不是很了解!我再次道歉。 - xtremethegamer
3个回答

7
您的异常提示表明,您无法反序列化没有默认(无参数)构造函数的类。
您要反序列化的类,或作为该类属性包含的某个类,具有带参数的构造函数并且没有默认构造函数。
反序列化程序无法创建该类的实例,因为它没有传递给该构造函数的参数。
如果不看您尝试反序列化的类的定义,我不能再提供更多帮助。
编辑: 看起来Newtonsoft.Json足够好,已经提供了一些属性,您几乎正确地使用它们,以解决此问题。 JsonConstructorAttribute将从正在进行反序列化的序列化字符串中匹配属性和构造函数参数,前提是名称匹配(忽略大小写)。
[JsonConstructor]
public Sellable(int value, string name){
    Value = value;
    Name = name;
}

感谢Brian Rogers的回答提供的线索,帮助我找到相关文档!


我添加了我正在尝试序列化的类。希望这足够了,正如编辑中所述,我是一名学生,对此事(JSON和序列化/反序列化)并不十分了解,并且我很少使用论坛寻求帮助。对此给您带来的不便深感抱歉! - xtremethegamer
问题在于你的三个类都只定义了带参数的构造函数,因此序列化程序无法创建这些类的实例。如果你为每个类添加另一个不带参数的构造函数,那么你的序列化程序就能够反序列化这些类了。我们都曾经是新手,你会成功的! :-) - Andy
我明白了,但是使用无参数构造函数会导致变量无法正确插入值,不是吗? - xtremethegamer
我看到你已经在使用[JsonConstructor](单词“Attribute”是可选的)。你需要确保参数名称与属性名称匹配。因此,list需要变成shopListval变成value等等。 - Andy
Bruno 抓得好!你不能反序列化一个接口或抽象类,所以你需要改变 Shop 不再是抽象的。 - Andy
显示剩余2条评论

0

错误信息准确地指出了问题所在以及如何解决。

反序列化的目标类(User 和 Shop)需要:

  • 一个无参构造函数(我认为这是最简单、最直接的方法);
  • 一个带参数的构造函数;或者
  • 一个带有 'JsonConstructorAttribute' 注释的带参数构造函数不被支持。

或者你可以将其反序列化为动态对象或对象,并映射属性(请参见 将 JSON 反序列化为 C# 动态对象?,其中包含一个小示例)。

此外,真的很让我困扰的是,为什么你要读取文件,然后再读取每一行呢? 你应该做一些像这样的事情

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

是的,您可以反序列化子对象并将其添加到父对象的集合中,没有问题。但是被反序列化的类不能是抽象类,因为您无法实例化它。在您的示例中,Furniture也是抽象的。 - Bruno Canettieri
哦,我明白了。没有仔细考虑这个问题。当您将反序列化到Shop类时,您有一个List<Sellable>,而反序列化程序不知道应在每个ShopList项上使用哪个实现(它应该是家具吗?其他?它根本没有办法知道)。就像在https://dotnetfiddle.net/fF4sRU中一样。在这种情况下,您将不得不告诉它如何区分它,您可以在https://attach2process.wordpress.com/2017/02/19/deserializing-a-json-to-a-list-of-abstract-types-or-interfaces-with-newtonsoft-json-net-converter/中获得一个粗略的示例。 - Bruno Canettieri
我尝试使用我正在使用的System.Text.Json进行调查。我成功地实现了它,但是当我现在运行序列化过程(传递serializeOptions参数)时,我会得到一个新的System.FormatException异常。我将新代码放在主帖中。 - xtremethegamer
你现在的代码与原始代码差别太大,这使得帮助你变得困难。但是,你应该从一步一步开始。首先只对一个Furniture进行序列化/反序列化,然后是List<Furniture>,再然后是List<Sellable>,最后是Shop(...)。此外,首先剥离这些类(让每个类只有一个具有字符串值的属性),然后逐步构建,直到找到错误为止。然后你将更清楚地知道是什么导致了错误(然后你可以提出更专注的问题)。 - Bruno Canettieri
我通过分解“商店”对象,序列化各个组件并在读取时重新创建它来解决了问题。非常感谢您的帮助! :) - xtremethegamer
显示剩余4条评论

0

我使用 Blazor WASM (Blazor WebAssembly) 和 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'。

使用您的模型,最简单的解决方法是添加一个无参构造函数:

public Sellable(){

}

如果您需要默认值,请使用构造函数链接,这也是有效的:

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

https://dev59.com/13I-5IYBdhLWcg3whYor#1814965


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接