Program Club

인터페이스 속성의 XML 직렬화

proclub 2020. 10. 11. 11:31
반응형

인터페이스 속성의 XML 직렬화


IModelObject (인터페이스) 유형의 속성을 가진 객체를 XML 직렬화하고 싶습니다 .

public class Example
{
    public IModelObject Model { get; set; }
}

이 클래스의 개체를 serialize하려고하면
"인터페이스이기 때문에 Example 형식의 멤버 Example.Model을 serialize 할 수 없습니다 . "라는 오류가 나타납니다 .

문제는 인터페이스를 직렬화 할 수 없다는 것임을 이해합니다. 그러나 구체적인 Model 객체 유형은 런타임까지 알 수 없습니다.

일 교체 IModelObject의 XMLInclude와 추상적 인 또는 콘크리트 종류 및 사용 상속과 인터페이스하는 것은 가능하지만, 못생긴 해결 방법처럼 보인다.

어떤 제안?


이것은 형식 정보가 출력에 포함되지 않는 선언적 직렬화의 내재 된 제한 일뿐입니다.

<Flibble Foo="10" />다시 변환하려고 할

public class Flibble { public object Foo { get; set; } }

serializer는 그것이 int, string, double (또는 다른 것)인지 어떻게 알 수 있습니까?

이 작업을 수행하려면 몇 가지 옵션이 있지만 런타임까지 진정으로 알지 못하는 경우 가장 쉬운 방법은 XmlAttributeOverrides를 사용하는 것 입니다.

슬프게도 이것은 인터페이스가 아닌 기본 클래스에서만 작동합니다. 당신이 할 수있는 최선은 당신의 필요에 충분하지 않은 재산을 무시하는 것입니다.

정말로 인터페이스를 유지해야한다면 세 가지 실제 옵션이 있습니다.

그것을 숨기고 다른 재산에서 처리하십시오

추악하고 불쾌한 보일러 플레이트와 많은 반복이지만 클래스의 대부분의 소비자는 문제를 다룰 필요가 없습니다.

[XmlIgnore()]
public object Foo { get; set; }

[XmlElement("Foo")]
[EditorVisibile(EditorVisibility.Advanced)]
public string FooSerialized 
{ 
  get { /* code here to convert any type in Foo to string */ } 
  set { /* code to parse out serialized value and make Foo an instance of the proper type*/ } 
}

이것은 유지 관리의 악몽이 될 것입니다 ...

IXmlSerializable 구현

첫 번째 옵션과 비슷하지만 모든 것을 제어 할 수 있지만

  • 장점
    • 당신은 주변에 거슬리는 '가짜'속성이 없습니다.
    • 유연성 / 버전 관리를 추가하는 xml 구조와 직접 상호 작용할 수 있습니다.
  • 단점
    • 클래스의 다른 모든 속성에 대해 휠을 다시 구현해야 할 수도 있습니다.

노력의 중복 문제는 첫 번째 문제와 유사합니다.

래핑 유형을 사용하도록 속성 수정

public sealed class XmlAnything<T> : IXmlSerializable
{
    public XmlAnything() {}
    public XmlAnything(T t) { this.Value = t;}
    public T Value {get; set;}

    public void WriteXml (XmlWriter writer)
    {
        if (Value == null)
        {
            writer.WriteAttributeString("type", "null");
            return;
        }
        Type type = this.Value.GetType();
        XmlSerializer serializer = new XmlSerializer(type);
        writer.WriteAttributeString("type", type.AssemblyQualifiedName);
        serializer.Serialize(writer, this.Value);   
    }

    public void ReadXml(XmlReader reader)
    {
        if(!reader.HasAttributes)
            throw new FormatException("expected a type attribute!");
        string type = reader.GetAttribute("type");
        reader.Read(); // consume the value
        if (type == "null")
            return;// leave T at default value
        XmlSerializer serializer = new XmlSerializer(Type.GetType(type));
        this.Value = (T)serializer.Deserialize(reader);
        reader.ReadEndElement();
    }

    public XmlSchema GetSchema() { return(null); }
}

이것을 사용하면 (프로젝트 P에서) 다음과 같은 것이 관련됩니다.

public namespace P
{
    public interface IFoo {}
    public class RealFoo : IFoo { public int X; }
    public class OtherFoo : IFoo { public double X; }

    public class Flibble
    {
        public XmlAnything<IFoo> Foo;
    }


    public static void Main(string[] args)
    {
        var x = new Flibble();
        x.Foo = new XmlAnything<IFoo>(new RealFoo());
        var s = new XmlSerializer(typeof(Flibble));
        var sw = new StringWriter();
        s.Serialize(sw, x);
        Console.WriteLine(sw);
    }
}

당신에게주는 :

<?xml version="1.0" encoding="utf-16"?>
<MainClass 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Foo type="P.RealFoo, P, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  <RealFoo>
   <X>0</X>
  </RealFoo>
 </Foo>
</MainClass>

이것은 많은 보일러 플레이트를 피하지만 클래스 사용자에게는 분명히 더 번거 롭습니다.

행복한 매체는 XmlAnything 아이디어를 첫 번째 기술의 'backing'속성에 병합하는 것일 수 있습니다. 이런 식으로 대부분의 지저분한 작업은 당신을 위해 이루어 지지만 동급의 소비자는 내 성과의 혼동 이상의 영향을받지 않습니다.


이에 대한 해결책은 DataContractSerializer와 함께 리플렉션을 사용하는 것입니다. [DataContract] 또는 [DataMember]로 클래스를 표시 할 필요도 없습니다. 인터페이스 유형 속성 (사전 포함)이 있는지 여부에 관계없이 모든 개체를 xml로 직렬화합니다. 다음은 인터페이스가 있어도 모든 객체를 XML로 직렬화하는 간단한 확장 메서드입니다 (재귀 적으로 실행되도록 조정할 수도 있습니다).

    public static XElement ToXML(this object o)
    {
        Type t = o.GetType();

        Type[] extraTypes = t.GetProperties()
            .Where(p => p.PropertyType.IsInterface)
            .Select(p => p.GetValue(o, null).GetType())
            .ToArray();

        DataContractSerializer serializer = new DataContractSerializer(t, extraTypes);
        StringWriter sw = new StringWriter();
        XmlTextWriter xw = new XmlTextWriter(sw);
        serializer.WriteObject(xw, o);
        return XElement.Parse(sw.ToString());
    }

LINQ식이하는 일은 각 속성을 열거하고, 인터페이스 인 각 속성을 반환하고, 해당 속성 (기본 개체)의 값을 가져오고, 구체적인 개체의 유형을 가져 와서 배열에 넣은 다음이를 serializer의 알려진 유형 목록.

이제 serializer는 직렬화하는 형식에 대해 알고 있으므로 작업을 수행 할 수 있습니다.


ExtendedXmlSerializer 를 사용할 수 있습니다 . 이 serializer는 트릭없이 인터페이스 속성의 직렬화를 지원합니다.

var serializer = new ConfigurationContainer().UseOptimizedNamespaces().Create();

var obj = new Example
                {
                    Model = new Model { Name = "name" }
                };

var xml = serializer.Serialize(obj);

XML은 다음과 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<Example xmlns:exs="https://extendedxmlserializer.github.io/v2" xmlns="clr-namespace:ExtendedXmlSerializer.Samples.Simple;assembly=ExtendedXmlSerializer.Samples">
    <Model exs:type="Model">
        <Name>name</Name>
    </Model>
</Example>

ExtendedXmlSerializer는 .net 4.5 및 .net Core를 지원합니다.


인터페이스 구현자를 미리 알고 있다면 파싱 코드를 작성하지 않고 인터페이스 유형을 직렬화하는 데 사용할 수있는 매우 간단한 해킹이 있습니다.

public interface IInterface {}
public class KnownImplementor01 : IInterface {}
public class KnownImplementor02 : IInterface {}
public class KnownImplementor03 : IInterface {}
public class ToSerialize {
  [XmlIgnore]
  public IInterface InterfaceProperty { get; set; }
  [XmlArray("interface")]
  [XmlArrayItem("ofTypeKnownImplementor01", typeof(KnownImplementor01))]
  [XmlArrayItem("ofTypeKnownImplementor02", typeof(KnownImplementor02))]
  [XmlArrayItem("ofTypeKnownImplementor03", typeof(KnownImplementor03))]
  public object[] InterfacePropertySerialization {
    get { return new[] { InterfaceProperty }; ; }
    set { InterfaceProperty = (IInterface)value.Single(); }
  }
}

결과 xml은 다음과 같은 라인을 따라야합니다.

 <interface><ofTypeKnownImplementor01><!-- etc... -->

IModelObject 인터페이스를 추상 또는 구체적인 유형으로 바꾸고 상속을 XMLInclude와 함께 사용하는 것은 가능하지만 추악한 해결 방법처럼 보입니다.

추상 기반을 사용할 수 있다면 그 경로를 권장합니다. 손으로 굴린 직렬화를 사용하는 것보다 여전히 깨끗합니다. 추상 기반에서 내가 보는 유일한 문제는 여전히 구체적인 유형이 필요하다는 것입니다. 적어도 그것이 내가 과거에 사용한 방법입니다.

public abstract class IHaveSomething
{
    public abstract string Something { get; set; }
}

public class MySomething : IHaveSomething
{
    string _sometext;
    public override string Something 
    { get { return _sometext; } set { _sometext = value; } }
}

[XmlRoot("abc")]
public class seriaized
{
    [XmlElement("item", typeof(MySomething))]
    public IHaveSomething data;
}

불행히도 serializer는 인터페이스에 대해 직렬화 할 항목을 모르기 때문에 간단한 대답이 없습니다. MSDN 에서이 문제를 해결하는 방법에 대한 자세한 설명을 찾았습니다.


Unfortuantely for me, I had a case where the class to be serialized had properties that had interfaces as properties as well, so I needed to recursively process each property. Also, some of the interface properties were marked as [XmlIgnore], so I wanted to skip over those. I took ideas that I found on this thread and added some things to it to make it recursive. Only the deserialization code is shown here:

void main()
{
    var serializer = GetDataContractSerializer<MyObjectWithCascadingInterfaces>();
    using (FileStream stream = new FileStream(xmlPath, FileMode.Open))
    {
        XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
        var obj = (MyObjectWithCascadingInterfaces)serializer.ReadObject(reader);

        // your code here
    }
}

DataContractSerializer GetDataContractSerializer<T>() where T : new()
{
    Type[] types = GetTypesForInterfaces<T>();

    // Filter out duplicates
    Type[] result = types.ToList().Distinct().ToList().ToArray();

    var obj = new T();
    return new DataContractSerializer(obj.GetType(), types);
}

Type[] GetTypesForInterfaces<T>() where T : new()
{
    return GetTypesForInterfaces(typeof(T));
}

Type[] GetTypesForInterfaces(Type T)
{
    Type[] result = new Type[0];
    var obj = Activator.CreateInstance(T);

    // get the type for all interface properties that are not marked as "XmlIgnore"
    Type[] types = T.GetProperties()
        .Where(p => p.PropertyType.IsInterface && 
            !p.GetCustomAttributes(typeof(System.Xml.Serialization.XmlIgnoreAttribute), false).Any())
        .Select(p => p.GetValue(obj, null).GetType())
        .ToArray();

    result = result.ToList().Concat(types.ToList()).ToArray();

    // do the same for each of the types identified
    foreach (Type t in types)
    {
        Type[] embeddedTypes = GetTypesForInterfaces(t);
        result = result.ToList().Concat(embeddedTypes.ToList()).ToArray();
    }
    return result;
}

in my project, I have a
List<IFormatStyle> FormatStyleTemplates;
containing different Types.

I then use the solution 'XmlAnything' from above, to serialize this list of different types. The generated xml is beautiful.

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [XmlArray("FormatStyleTemplates")]
    [XmlArrayItem("FormatStyle")]
    public XmlAnything<IFormatStyle>[] FormatStyleTemplatesXML
    {
        get
        {
            return FormatStyleTemplates.Select(t => new XmlAnything<IFormatStyle>(t)).ToArray();
        }
        set
        {
            // read the values back into some new object or whatever
            m_FormatStyleTemplates = new FormatStyleProvider(null, true);
            value.ForEach(t => m_FormatStyleTemplates.Add(t.Value));
        }
    }

I have found a simpler solution (you don't need the DataContractSerializer), thanks to this blog here: XML serializing derived types when base type is in another namespace or DLL

But 2 problems can rise in this implementation:

(1) What if DerivedBase is not in the namespace of class Base, or even worse in a project that depends on Base namespace, so Base cannot XMLInclude DerivedBase

(2) What if we only have class Base as a dll ,so again Base cannot XMLInclude DerivedBase

Till now, ...

So the solution to the 2 problems is by using XmlSerializer Constructor (Type, array[]) :

XmlSerializer ser = new XmlSerializer(typeof(A), new Type[]{ typeof(DerivedBase)});

A detailed example is provided here on MSDN: XmlSerializer Constructor (Type, extraTypesArray[])

It seems to me that for DataContracts or Soap XMLs, you need to check the XmlRoot as mentioned here in this SO question.

A similar answer is here on SO but it isn't marked as one, as it not the OP seems to have considered it already.

참고URL : https://stackoverflow.com/questions/1333864/xml-serialization-of-interface-property

반응형