How to serialize property of type Object with XmlSerializer

前端 未结 3 2004
我寻月下人不归
我寻月下人不归 2020-12-10 17:10

I have a property:

public object Tag

but it can contain finite number of types, unfortunately without base type (except object type). But w

相关标签:
3条回答
  • 2020-12-10 17:55

    I did it implementing the IXmlSerializable interface, writing the object type as an element attribute.

      public void ReadXml(XmlReader reader)
      {
         reader.MoveToContent();
    
         Boolean isEmptyElement = reader.IsEmptyElement; 
         reader.ReadStartElement();
         if (!isEmptyElement)
         {
    
            // ...here comes all other properties deserialization
    
            object tag;
            if (ReadXmlObjectProperty(reader, "Tag", out tag))
            {
               Tag = tag;
            }
            reader.ReadEndElement();
         }
      }
    
      public void WriteXml(XmlWriter writer)
      {
    
         // ...here comes all other properties serialization
    
         WriteXmlObjectProperty(writer, "Tag", Tag);
      }
    
      public static bool ReadXmlObjectProperty(XmlReader reader, 
                                               string name,
                                               out object value)
      {
         value = null;
    
         // Moves to the element
         while (!reader.IsStartElement(name))
         {
            return false;
         }
         // Get the serialized type
         string typeName = reader.GetAttribute("Type");
    
         Boolean isEmptyElement = reader.IsEmptyElement; 
         reader.ReadStartElement();
         if (!isEmptyElement)
         {
            Type type = Type.GetType(typeName);
    
            if (type != null)
            {
               // Deserialize it
               XmlSerializer serializer = new XmlSerializer(type);
               value = serializer.Deserialize(reader);
            }
            else
            {
               // Type not found within this namespace: get the raw string!
               string xmlTypeName = typeName.Substring(typeName.LastIndexOf('.')+1);
               value = reader.ReadElementString(xmlTypeName);
            }
            reader.ReadEndElement();
         }
    
         return true;
      }
      public static void WriteXmlObjectProperty(XmlWriter writer, 
                                                string name,
                                                object value)
      {
         if (value != null)
         {
            Type valueType = value.GetType();
            writer.WriteStartElement(name);
            writer.WriteAttributeString("Type", valueType.FullName);
            writer.WriteRaw(ToXmlString(value, valueType));
            writer.WriteFullEndElement();
         }
      }
    
      public static string ToXmlString(object item, Type type) 
      {
         XmlWriterSettings settings = new XmlWriterSettings();
         settings.Encoding = Encoding.ASCII;
         settings.Indent = true;
         settings.OmitXmlDeclaration = true;
         settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
    
         using(StringWriter textWriter = new StringWriter()) 
         using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) 
         {
            XmlSerializer serializer = new XmlSerializer(type);
            serializer.Serialize(xmlWriter, item, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
            return textWriter.ToString();
         }
      }
    

    Note: in the code I use no namespace and ASCII encoding, those are non mandatory choices.

    HTH, Cabbi

    0 讨论(0)
  • 2020-12-10 18:08

    You can also use [XmlInclude(typeof(YourType))] on the class that contains the object property. So in the case of the OP, it would look like this

    [XmlInclude(typeof(PossibleClassOne))]
    [XmlInclude(typeof(PossibleClassTwo))]
    public class MyClass
    {
       public object Tag { get; set; }
    }
    

    This way, you can keep your element name <Tag> in all cases

    0 讨论(0)
  • 2020-12-10 18:12

    I don't recommend this, but yes, you can use [XmlElement] etc to tell it about multiple candidate types for a member:

    public class Test
    {
        private static void Main()
        {
            var ser = new XmlSerializer(typeof (Test));
            var obj = new Test {Value = "abc"};
            ser.Serialize(Console.Out, obj);
            obj = new Test { Value = 123 };
            ser.Serialize(Console.Out, obj);
            obj = new Test { Value = 456.7F };
            ser.Serialize(Console.Out, obj);
        }
    
        [XmlElement("a", Type = typeof(int))]
        [XmlElement("b", Type = typeof(string))]
        [XmlElement("c", Type = typeof(float))]
        public object Value { get; set; }
    }
    

    The important bits of the output (ignoring all the xmlns / <?xml> etc) are:

    <Test>
      <b>abc</b>
    </Test>
    
    <Test>
      <a>123</a>
    </Test>
    
    <Test>
      <c>456.7</c>
    </Test>
    
    0 讨论(0)
提交回复
热议问题