将对象序列到JSON后,我们如何保留多态性

发布于 2025-01-27 22:33:59 字数 1468 浏览 1 评论 0原文

在将配置文件保存到数据库之后,我们在系统中存在一个复杂的问题。

为了这个问题,我们简化了问题。请参阅下面的代码。

这是代码:

class Wheel
{
    public virtual string GetWidth()
    {
        return "Normal";
    }
}

class SmallWheel : Wheel
{
    public override string GetWidth()
    {
        return "Small";
    }
}

class BigWheel : Wheel
{
    public override string GetWidth()
    {
        return "Big";
    }
}

public static async Task Main(string[] args)
{
    //Build list of wheels
    var wheels = new List<Wheel>()
    {
        new Wheel(),
        new SmallWheel(),
        new BigWheel(),
    };

    //We print wheels to check it works
    foreach (var x in wheels)
    {
        Console.WriteLine(x.GetWidth());
    }

    //Save the list to the db as a string
    var file = JsonConvert.SerializeObject(wheels);

    //We just use file instead of db for simplictiydf

    //Later we read the config file from the DB
    var wheelsFromDb = JsonConvert.DeserializeObject<List<Wheel>>(file);

    //We now want to print out our wheels.
    Console.WriteLine("Printing WHeels from Db");
    foreach (var x in wheelsFromDb)
    {
        Console.WriteLine(x.GetWidth());
    }
}

当我运行时的结果:

Normal
Small
Big

Printing WHeels from Db
Normal
Normal
Normal

现在,如您所见,我们正在丢失De-serialise化后的车轮类型。

我们如何解决这个问题?

从本质上讲,我们需要存储一个带有多个儿童课程的配置文件,每个类别都具有覆盖功能。我意识到,当JSON对原始字符串的估计时,它没有以前哪种类型的上下文。有什么方法可以存储该上下文或使用其他库或数据库?

We have a complex issue in our system regarding retaining polymorphism after we have saved a config file to the database.

We have simplified the problem for the sake of this question. See code below.

Here is the code:

class Wheel
{
    public virtual string GetWidth()
    {
        return "Normal";
    }
}

class SmallWheel : Wheel
{
    public override string GetWidth()
    {
        return "Small";
    }
}

class BigWheel : Wheel
{
    public override string GetWidth()
    {
        return "Big";
    }
}

public static async Task Main(string[] args)
{
    //Build list of wheels
    var wheels = new List<Wheel>()
    {
        new Wheel(),
        new SmallWheel(),
        new BigWheel(),
    };

    //We print wheels to check it works
    foreach (var x in wheels)
    {
        Console.WriteLine(x.GetWidth());
    }

    //Save the list to the db as a string
    var file = JsonConvert.SerializeObject(wheels);

    //We just use file instead of db for simplictiydf

    //Later we read the config file from the DB
    var wheelsFromDb = JsonConvert.DeserializeObject<List<Wheel>>(file);

    //We now want to print out our wheels.
    Console.WriteLine("Printing WHeels from Db");
    foreach (var x in wheelsFromDb)
    {
        Console.WriteLine(x.GetWidth());
    }
}

Results when I run it:

Normal
Small
Big

Printing WHeels from Db
Normal
Normal
Normal

Now as you can see we are losing what type the wheel is after de-serialisation.

How can we go about solving this issue?

In essence, we need to store a config file with multiple children classes each with overrides functions. I realise that when Json deserializes the raw string it has no context of what type the class was before. Is there a way we can store that context or use a different library or database?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

岛歌少女 2025-02-03 22:33:59

我正在将此代码用于派生类列表。您可以使用基类,而不是界面以及

IAnimal[] animals = new IAnimal[] {
                    new Cat{CatName="Tom"},
                    new Dog{DogName="Scoopy"},
                    new Rabbit{RabitName="Honey"}
    };

    var jsonSerializerSettings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All
    };
    var json = JsonConvert.SerializeObject(animals, jsonSerializerSettings);
    

    List<IAnimal> animalsBack = ((JArray)JsonConvert.DeserializeObject(json)).Select(o => (IAnimal)JsonConvert.DeserializeObject(o.ToString(), Type.GetType((string)o["$type"]))).ToList();

public interface IAnimal
{
}

public class Animal : IAnimal
{
}
    
public class Cat : IAnimal { public string CatName { get; set; } }
public class Dog : IAnimal { public string DogName { get; set; } }
public class Rabbit : IAnimal { public string RabitName { get; set; } }

I am using this code for a list of derived classes. You can use a base class instead of an interface as well

IAnimal[] animals = new IAnimal[] {
                    new Cat{CatName="Tom"},
                    new Dog{DogName="Scoopy"},
                    new Rabbit{RabitName="Honey"}
    };

    var jsonSerializerSettings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All
    };
    var json = JsonConvert.SerializeObject(animals, jsonSerializerSettings);
    

    List<IAnimal> animalsBack = ((JArray)JsonConvert.DeserializeObject(json)).Select(o => (IAnimal)JsonConvert.DeserializeObject(o.ToString(), Type.GetType((string)o["$type"]))).ToList();

classes

public interface IAnimal
{
}

public class Animal : IAnimal
{
}
    
public class Cat : IAnimal { public string CatName { get; set; } }
public class Dog : IAnimal { public string DogName { get; set; } }
public class Rabbit : IAnimal { public string RabitName { get; set; } }
山田美奈子 2025-02-03 22:33:59

JSONCONVERT在使用typenamehandling.all时,在引擎盖下创建属性。

因此,我们可以通过创建类中的size属性来为编译器提供有关哪种类型的线索:

public class Wheel
{
    public virtual string Size { get;  set; } = WheelSize.NORMAL;

    public virtual string GetWidth() => Size;
    
}

public class SmallWheel : Wheel
{
    public override string Size { get; set; } = WheelSize.SMALL;
}

public class BigWheel : Wheel
{
    override public string Size { get; set; } = WheelSize.BIG;
}

这是一个包含车轮大小的类:

public class WheelSize
{
    public static string SMALL = "small";
    public static string NORMAL = "normal";
    public static string BIG = "big";
}

到目前为止,太好了。但是,我们如何根据尺寸的车轮对JSON进行测试?我们可以编写Custom Deserializer:

internal class WheelJsonConverter : JsonConverter
{
    private readonly Type[] _types;

    public WheelJsonConverter(params Type[] types)
    {
        _types = types;
    }

    public override object ReadJson(JsonReader reader, Type objectType, 
        object existingValue, JsonSerializer serializer)
    {
        List<Wheel> wheels = new List<Wheel>();
        WheelToSize wheelToSize = new WheelToSize();

        JArray jArray = JArray.Load(reader);
        foreach (JToken token in jArray)
        {
            string wheelSize = token["size"].ToString();
            Wheel wheel =  wheelToSize.WheelBySize[wheelSize];
            wheels.Add(wheel);
        }

        return wheels;
    }


    public override bool CanConvert(Type objectType)
    {   
        if (objectType.IsGenericType 
            && IsGenericTypeArgumentTheSame(objectType))            
            return true;
        
        return false;
    }

    private bool IsGenericTypeArgumentTheSame(Type objectType) => 
        objectType.GenericTypeArguments[0] == _types[0];

    public override void WriteJson(JsonWriter writer, object? value, 
        JsonSerializer serializer)
    {
        JToken t = JToken.FromObject(value);

        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
        }
        else
        {
            JObject o = (JObject)t;
            o.WriteTo(writer);
        }
    }
}

这是一个在较低情况下创建JSON的类:

public class LowercaseContractResolver : DefaultContractResolver
{
    protected override string ResolvePropertyName(string propertyName)
    {
        return propertyName.ToLower();
    }
}   

这是一个基于大小创建对象的工厂:

public class WheelToSize
{
    public Dictionary<string, Wheel> WheelBySize { get; private set; } = new()
    {
        { WheelSize.NORMAL, new Wheel() },
        { WheelSize.BIG, new BigWheel() },
        { WheelSize.SMALL, new SmallWheel() }
    };
}

然后您可以运行代码:

List<Wheel> wheels = new List<Wheel>()
{
    new Wheel(),
    new SmallWheel(),
    new BigWheel(),
};

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new LowercaseContractResolver();
var file = JsonConvert.SerializeObject(wheels, Formatting.Indented, settings);

var wheelsFromDb = JsonConvert.DeserializeObject<List<Wheel>>(file, 
    new WheelJsonConverter(typeof(Wheel)));

//We now want to print out our wheels.
Console.WriteLine("Printing WHeels from Db");
foreach (var x in wheelsFromDb)
    Console.WriteLine(x.GetWidth());

JsonConvert creates property $type under the hood when TypeNameHandling.All is used.

So we can give a clue to compiler about what type actually is by creating Size property in classes:

public class Wheel
{
    public virtual string Size { get;  set; } = WheelSize.NORMAL;

    public virtual string GetWidth() => Size;
    
}

public class SmallWheel : Wheel
{
    public override string Size { get; set; } = WheelSize.SMALL;
}

public class BigWheel : Wheel
{
    override public string Size { get; set; } = WheelSize.BIG;
}

This is a class which contains size of wheels:

public class WheelSize
{
    public static string SMALL = "small";
    public static string NORMAL = "normal";
    public static string BIG = "big";
}

So far, so good. But how we can deserialize json based on size wheel? We can write custom deserializer:

internal class WheelJsonConverter : JsonConverter
{
    private readonly Type[] _types;

    public WheelJsonConverter(params Type[] types)
    {
        _types = types;
    }

    public override object ReadJson(JsonReader reader, Type objectType, 
        object existingValue, JsonSerializer serializer)
    {
        List<Wheel> wheels = new List<Wheel>();
        WheelToSize wheelToSize = new WheelToSize();

        JArray jArray = JArray.Load(reader);
        foreach (JToken token in jArray)
        {
            string wheelSize = token["size"].ToString();
            Wheel wheel =  wheelToSize.WheelBySize[wheelSize];
            wheels.Add(wheel);
        }

        return wheels;
    }


    public override bool CanConvert(Type objectType)
    {   
        if (objectType.IsGenericType 
            && IsGenericTypeArgumentTheSame(objectType))            
            return true;
        
        return false;
    }

    private bool IsGenericTypeArgumentTheSame(Type objectType) => 
        objectType.GenericTypeArguments[0] == _types[0];

    public override void WriteJson(JsonWriter writer, object? value, 
        JsonSerializer serializer)
    {
        JToken t = JToken.FromObject(value);

        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
        }
        else
        {
            JObject o = (JObject)t;
            o.WriteTo(writer);
        }
    }
}

This is a class to create json in lower case:

public class LowercaseContractResolver : DefaultContractResolver
{
    protected override string ResolvePropertyName(string propertyName)
    {
        return propertyName.ToLower();
    }
}   

This is a factory which creates an object based on size:

public class WheelToSize
{
    public Dictionary<string, Wheel> WheelBySize { get; private set; } = new()
    {
        { WheelSize.NORMAL, new Wheel() },
        { WheelSize.BIG, new BigWheel() },
        { WheelSize.SMALL, new SmallWheel() }
    };
}

And then you can run code like this:

List<Wheel> wheels = new List<Wheel>()
{
    new Wheel(),
    new SmallWheel(),
    new BigWheel(),
};

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new LowercaseContractResolver();
var file = JsonConvert.SerializeObject(wheels, Formatting.Indented, settings);

var wheelsFromDb = JsonConvert.DeserializeObject<List<Wheel>>(file, 
    new WheelJsonConverter(typeof(Wheel)));

//We now want to print out our wheels.
Console.WriteLine("Printing WHeels from Db");
foreach (var x in wheelsFromDb)
    Console.WriteLine(x.GetWidth());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文