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


83

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

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

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

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

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

어떤 제안?

답변:


116

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

<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'속성에 병합하는 것일 수 있습니다. 이런 식으로 대부분의 지저분한 작업이 당신을 위해 이루어 지지만 동급의 소비자는 자기 성찰과의 혼동 이상의 영향을받지 않습니다.


나는 당신의 접근 방식 마녀 래핑 속성을 구현하려고 시도했지만 불행히도 문제가 있습니다 :(이 게시물을 살펴 주시겠습니까? stackoverflow.com/questions/7584922/…
SOReader

FooSerialized 속성을 소개하는 기술이 있습니까?
Gqqnbig

42

이에 대한 해결책은 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는 직렬화하는 형식에 대해 알고 있으므로 작업을 수행 할 수 있습니다.


문제에 대한 매우 우아하고 쉬운 해결책. 감사!
Ghlouw

2
일반 IList 및 인터페이스에 대해 작동하지 않는 것 같습니다. 예 : IList <IMyInterface>. IMyInterface에 대한 concreate 값을 KnownTypes에 추가해야하지만 대신 IList <IMyInterface>가 추가됩니다.
galford13x

6
@ galford13x 저는이 예제를 가능한 한 간단하게 만들면서도 요점을 보여 주려고했습니다. 재귀 또는 인터페이스 유형과 같은 단일 케이스를 추가하면 읽기가 덜 명확 해지고 주요 지점에서 벗어날 수 있습니다. 필요한 알려진 유형을 가져 오기 위해 추가 검사를 자유롭게 추가하십시오. 솔직히 말해서 리플렉션을 사용하여 얻을 수없는 것은 없다고 생각합니다. 예를 들어 일반 매개 변수의 유형 인 stackoverflow.com/questions/557340/…
Despertar

나는 질문이 인터페이스 직렬화를 요청했기 때문에 이것을 언급했다는 것을 이해합니다. 나는 다른 사람들에게 그들의 머리가 부딪히는 것을 방지하기 위해 수정없이 오류가 예상된다는 것을 다른 사람들에게 알릴 것이라고 생각했습니다. 그러나 [KnownType ()] 속성을 추가하고 귀하의 코드가 결과로 이어 지므로 귀하의 코드에 감사드립니다.
galford13x 2013 년

1
직렬화 할 때 namesapce를 생략하는 방법이 있습니까? 대신 xmlwriter를 사용하여 xmlwriterSettings를 사용하려고했지만 추가 유형을 전달할 수있는 오버로드를 사용했지만 작동하지 않습니다 ...
Legends

9

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

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... -->

1
매우 유용합니다. 감사합니다. 대부분의 상황에서 인터페이스를 구현하는 클래스를 알고 있습니다. 이 대답은 더 높아야합니다.
Jonah

이것이 가장 쉬운 해결책입니다. 감사합니다!
mKay

8

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를 지원합니다.


3

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;
}

2

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


1

불행히도 직렬화 할 클래스에 속성과 인터페이스가있는 속성이있는 경우가 있었기 때문에 각 속성을 재귀 적으로 처리해야했습니다. 또한 일부 인터페이스 속성은 [XmlIgnore]로 표시되어 있으므로 건너 뛰고 싶었습니다. 이 스레드에서 찾은 아이디어를 가져와 재귀 적으로 만들기 위해 몇 가지를 추가했습니다. deserialization 코드 만 여기에 표시됩니다.

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;
}

1

이 블로그 덕분에 더 간단한 솔루션 (DataContractSerializer가 필요하지 않음)을 찾았습니다. 기본 유형이 다른 네임 스페이스 또는 DLL에있을 때 파생 된 유형을 직렬화하는 XML

그러나이 구현에서는 두 가지 문제가 발생할 수 있습니다.

(1) DerivedBase가 Base 클래스의 네임 스페이스에 없거나 Base 네임 스페이스에 의존하는 프로젝트에서 더 나쁘면 Base가 DerivedBase를 XMLInclude 할 수 없습니다.

(2) Base 클래스 만 dll로 사용하면 Base는 DerivedBase를 포함 할 수 없습니다.

지금까지, ...

따라서 두 가지 문제에 대한 해결책은 XmlSerializer 생성자 (Type, array [])를 사용하는 것입니다 .

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

MSDN에서 자세한 예가 제공됩니다. XmlSerializer Constructor (Type, extraTypesArray [])

DataContracts 또는 Soap XML의 경우이 SO 질문에서 언급 한대로 XmlRoot확인 해야합니다. .

비슷한 대답은 여기에 SO 켜져 있지만 그렇지 영업 이익 이미 간주 한 것으로 보인다 그것은 하나의로 표시되지 않습니다.


0

내 프로젝트에는
List <IFormatStyle> FormatStyleTemplates가 있습니다.
다른 유형을 포함합니다.

그런 다음 위의 'XmlAnything'솔루션을 사용하여이 다른 유형 목록을 직렬화합니다. 생성 된 xml은 아름답습니다.

    [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));
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.