在运行时决定序列化哪些属性

8
假设我需要对一个名为Car的类进行分级序列化,比如:内部级别和公共级别。公共级别中的一些属性不应该被序列化,因为它们是内部的。
目前我能想到的“最简单”的方法是使用继承:
class CarPublic {
  public int PropX {get;set}
}

class CarInternal: CarPublic {
  public string PropY {get;set}
}

然后我可以:
object ToSerialize() {
 CarInternal car = GetCar();
 if( level == Level.Public ) { 
    return car as CarPublic;
 } else {
     return car;
 }
}

ToSerialize()的结果由一个框架(我无法控制)接收并序列化为JSON或XML。
为了简单起见,我省略了XML序列化属性。
这感觉像是一种黑客行为,而黑客只能带你走那么远。有更好的方法(方式?)来实现这个吗?
我想现在很清楚了,但我想避免编写自己的JSON和XML序列化方法。
提前致谢 Tymek
==编辑
澄清一下,我想能够序列化多个级别:
class Car0 {
  public int PropA {get;set}
}

class Car1: Car0 {
  public string PropB {get;set}
}

class Car2: Car1 {
  public int PropC {get;set}
}

class Car3: Car2 {
  public string PropD {get;set}
}

而且
object ToSerialize( Level level ) {
 Car3 car = GetCar();
 switch( level ) {
   case Level.Zero: return car as Car0;
   case Level.One: return car as Car1;
   case Level.Two: return car as Car3;
   case Level.Three: return car as Car4;
 }
 return null;
}

== 选择的方法

我选择了Marc Gravell的答案作为最佳答案,因为它提供了有关C#及其“标准”组件如何支持我所要求的内容的通用信息。

不过,我认为解决我的问题的最佳方法是使用上述示例中显示的代理类,并将该类以多级模式进行序列化,使用如下所示的方法。

public interface ICar {
    Car0 As0();
    Car1 As1();
    Car2 As2();
    Car3 As3();
 ...
 }

这使得Car0..3类非常简单,只包含属性,易于维护和理解。

1
编写自定义序列化方法是我所知道的更好的方法(而且真的并不难)。你有自定义序列化要求,这很适合使用自定义序列化器... 有没有特定的原因你不想走这条路? - Bill
主要原因是我处于一个框架环境中,序列化应该由框架完成,而我只需返回结果作为对象即可。 - tymtam
你能看到这个环境中其他类是如何解决这个问题的吗?如果你要把它砍成碎片,那么最好和其他人一样砍它 :) - Bill
哈!我是第一个做“根据级别公开属性”的人。 - tymtam
我认为现在是时候更新序列化框架以满足新的需求了 :) - Bill
2个回答

7
这主要取决于你使用的序列化框架。你提到了xml和json - 首先需要注意的是可以直接添加以下标记来进行装饰:
[XmlIgnore]
public int PropX {get;set;}

或者

[ScriptIgnore]
public int PropX {get;set;}

XmlSerializerJavascriptSerializer都会响应的是XML序列化格式。如果您需要根据每个实例进行决策,则可以使用ShouldSerialize**Specified模式:

public bool ShouldSerializePropX() {
   // return true to serialize, false to omit
}

以上是基于名称的模式,由 XmlSerializer 和其他程序使用; 它有一个孪生兄弟:
[XmlIgnore, Browsable(false)]
public bool PropXSpecified {
    get { /* return true to serialize, false to omit */ }
    set { /* can just drop this value - don't need to assign */ }
}

您不需要做任何事情来连接它们 - 它们会自动工作。

不同的序列化器允许不同的模式。

此外,有时您可以在运行时添加像[XmlIgnore]这样的内容 - 例如通过XmlAttributeOverrides,或针对任何给定序列化器的等效方式。


我将此标记为答案,因为它回答了问题,但我在问题本身中描述了我选择的方法,即==所选方法。 - tymtam

0
你可以使用自定义属性来装饰你的内部属性,表明它们应该被包含(或根据你的要求忽略),然后在你的ToSerialize中检查这个属性。
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ShouldSerializeAttribute : Attribute { }

那么你最后的类定义应该是这样的:

class Car 
{
    [ShouldSerialize]
    public int PropX {get;set}

    // This property won't be serialized because it is internal
    public int PropY { get; set; }
}

你的ToSerialize看起来应该是这样的:

object ToSerialize() 
{
    Car car = GetCar();

    foreach(PropertyInfo propInfo in car.GetType().GetProperties())
    {
        if(ShouldSerialize(propInfo)) 
        { 
            return car;
        }
    }
}

ShouldSerialize 可能看起来像这样:

internal bool ShouldSerialize(PropertyInfo propInfo)
{
    return propInfo.GetCustomAttributes(typeof(ShouldSerializeAttribute), true).FirstOrDefault() != null;
}

更新

根据@Bill在评论中的见解。如果您只想在levelLevel.Public时序列化公共属性,您可以通过使用BindingFlags.DeclaredOnly标志反射类型的属性来实现该效果:

foreach(PropertyInfo propInfo in car.GetType().GetProperties(BindingFlags.DeclaredOnly))

这应该返回仅由当前 car 实例声明的属性列表。


1
我认为这并没有回答问题。他需要根据所需的级别序列化不同的属性集,而不仅仅是序列化公共属性。 - Bill
@Bill - 问题中哪里说了这一点(或者在我回答时说了这一点)?OP发布的代码不会实现这个目标,那么是什么让你认为这就是他们想要的呢?除非我误读了代码的意图。 - M.Babcock
在他的代码中,他返回CarInternal(所有属性都被序列化)或CarPublic(只有公共属性被序列化)。请参见他的ToSerialize方法,该方法基于Level.Public进行选择。 - Bill
@Bill - 你可能是对的(我简化了情况,因为我没有考虑到这种可能性)。我会相应地更新。 - M.Babcock

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