【问题标题】:JavascriptSerializer serializing property twice when "new" used in subclassJavascriptSerializer 在子类中使用“新”时将属性序列化两次
【发布时间】:2014-10-06 11:46:12
【问题描述】:

尝试创建一个服务以返回一个具有许多共享属性的对象,但在某些情况下一个属性应该受到高度限制。这会导致奇怪和不受欢迎的行为,其中属性名称在序列化输出中被重用,从而导致浏览器中的行为不正确。下面是一个例子,可以粘贴到LINQPad中(如果你添加了对System.Web.Extensions的引用)来查看问题:

void Main()
{
    System.Web.Script.Serialization.JavaScriptSerializer json = new System.Web.Script.Serialization.JavaScriptSerializer();

    json.Serialize(new Full(true)).Dump();
    json.Serialize(new Limited()).Dump();
}

public class Full
{
    public String Stuff { get { return "Common things"; }  }
    public FullStatus Status { get; set; }

    public Full(bool includestatus)
    {
        if(includestatus)
            Status = new FullStatus();
    }
}

public class Limited : Full
{
    public new LimitedStatus Status { get; set; }

    public Limited() : base(false)
    {
        Status = new LimitedStatus();
    }
}

public class FullStatus 
{
    public String Text { get { return "Loads and loads and loads of things"; } }
}

public class LimitedStatus 
{
    public String Text { get { return "A few things"; } }
}

打印出来:

{"Stuff":"Common things","Status":{"Text":"Loads and loads and loads of things"}}
{"Status":{"Text":"A few things"},"Stuff":"Common things","Status":null}

在浏览器中调用 JSON.parse 时,第二个 Status 会覆盖第一个,这意味着 Status 始终为 null。

我能看到解决此问题的唯一方法是重构,以便 FullStatus 和 LimitedStatus 都从一个共同的父级继承并使用override 而不是new - 在现实世界的代码中有点复杂,但可能。我的假设正确吗?而且,我很想知道这是预期的行为还是错误。

【问题讨论】:

    标签: c# json serialization javascriptserializer


    【解决方案1】:

    是的,你的假设是正确的。 new 关键字与override 不同;它只是“隐藏”了基类属性,但原始属性仍然存在并且仍然可以通过反射发现(这就是序列化程序的工作方式)。

    一般来说,在基类中定义一个方法或属性,然后用派生类中的另一个方法或属性替换它,从而带走基类的功能,这被认为是“code smell”。这违反了Liskov Substitution Principle

    我建议不要从Full 派生Limited,而是为它们创建一个抽象基类并将常见的东西放在那里。然后,您可以向每个子类添加不同或独有的内容(即您的不同类型的 Status 成员)。

    例如:

    class Program
    {
        static void Main(string[] args)
        {
            System.Web.Script.Serialization.JavaScriptSerializer json = 
                new System.Web.Script.Serialization.JavaScriptSerializer();
    
            Console.WriteLine(json.Serialize(new Full(true)));
            Console.WriteLine(json.Serialize(new Limited()));
        }
    }
    
    public abstract class Base
    {
        public String Stuff { get { return "Common things"; } }
    }
    
    public class Full : Base
    {
        public FullStatus Status { get; set; }
    
        public Full(bool includestatus)
        {
            if (includestatus)
                Status = new FullStatus();
        }
    }
    
    public class Limited : Base
    {
        public LimitedStatus Status { get; set; }
    
        public Limited()
        {
            Status = new LimitedStatus();
        }
    }
    
    public class FullStatus
    {
        public String Text { get { return "Loads and loads and loads of things"; } }
    }
    
    public class LimitedStatus
    {
        public String Text { get { return "A few things"; } }
    }
    

    输出:

    {"Status":{"Text":"Loads and loads and loads of things"},"Stuff":"Common things"}
    {"Status":{"Text":"A few things"},"Stuff":"Common things"}
    

    【讨论】:

    • 感谢您的链接!我将您的答案标记为正确,因为它是我所走的路线 - 适合我首先尝试快速破解某些东西。我仍然对序列化程序行为背后的原因感兴趣,但我猜由于生成的 JSON 是完全有效的(好吧,至少在 Chrome 中 - 早期的属性被默默忽略)它不是序列化程序中的错误,只是一些东西供程序员修复他们的模型。
    • 你的意思是为什么序列化器会在同一个对象中输出两个同名的属性?通常不会,但是当您使用“new”关键字隐藏属性时,序列化程序在反映对象时会找到两个具有相同名称的属性,我猜它在写入时没有对这种情况进行任何检查JSON。根据the spec,在 JSON 中的同一个对象中拥有两个同名的属性在技术上并不是无效的,但它确实使 JSON 的互操作性降低。 (续)
    • 具有讽刺意味的是,一些解析器,包括 Json.Net 本身,在反序列化期间无法处理包含重复属性名称的对象。出于这个原因,使用重复的属性名称创建 JSON 被认为是一种错误的形式,最好尽可能避免。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多