0

Lets say I have the following simple class:

[XmlRoot]
[XmlType("string")]
public partial class eString : IEnumerable<char>
{
    string AsString {get;set;}
    public IEnumerator<char> GetEnumerator()
    {
        return this.AsString.ToCharArray().ToList().GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.AsString.ToCharArray().GetEnumerator();
    }
    public void Add(char character)
    {
        this.AsString += character;
    }
}

Also a namedValue class:

[XmlRoot]
public partial class eNamedValue: KeyValuePair<eString,object>
{...}

And finally,

[XmlRoot]
public partial class eList<T>: List<eNamedValue>
{...}

Now, I serialize eList or any class that inherits from eList using the following XML serializer:

    public static XDocument Serialize<T>(this T obj) where T: new()
    {
        Type tt = obj.GetType();
        XmlSerializer xsSubmit = new XmlSerializer(tt);
        StringWriter sww = new StringWriter();
        XmlWriter writer = XmlWriter.Create(sww);
        xsSubmit.Serialize(writer, obj);
        return XDocument.Parse(sww.ToString());
    }

Serialization works - but my eString is being serialized as a character array, so instead of getting "string", I get individual characters values:

<eNamedList>
  <Key>99</Key>
  <Key>111</Key>
  <Key>100</Key>
  <Key>101</Key>...

Also, on Deserialization (SOLVED, see Update#1 below):

public static T Deserialize<T>(this XDocument xml) where T: new()
    {
        var serializer = new XmlSerializer(typeof(T));
        T result;

        using (TextReader reader = new StringReader(xml.ToString()))
        {
            result = (T)serializer.Deserialize(reader);
        }
        return result;
    }

I receive the following error:

System.Runtime.Serialization.SerializationException: Error in line 1 position 111. Expecting element 'eNamedList' from namespace 'http://schemas.datacontract.org/2004/07/Epic'.. Encountered 'Element' with name 'eNamedList', namespace ''.

So, my questions become:

  1. How do I control serialization of my eString, or any IEnumerable<char> and always serialize as a string?
  2. Why, when the element names match, do I get a failure on deserialization? (Am I just missing a namespace declaration?)

Thanks!


Update #1: So, I removed the IEnumerable<char> interface from my eString, and just left the IEnumerable<char>.GetEnumerator() method, which allows my string to be used AS an IEnumerable<char> while in a foreach loop, but serializes as a string. #WIN

Also, thanks to dbc, updated the original post with the XML Deserializer (rather than DataContract Serializer) and deserialization works.

2
  • 1
    Why are you using XmlSerializer to serialize but DataContractSerializer to deserialize? Is that intentional? Commented Jul 21, 2015 at 20:00
  • Thanks - You helped me notice I'd mixed up my DataContract serializer and XMLSerializer methods. Updated my post Commented Jul 21, 2015 at 21:16

1 Answer 1

3

To answer your questions:

  1. You probably shouldn't reinvent the wheel with custom string solutions. Regardless, if you want an (insanely) high-level of control over your (de-)serialization, you could implement IXmlSerializable and do the exact same thing yourself.

    [XmlRoot("string-wrapper")]
    public class CustomString : IXmlSerializable
    {
        public string Value { get; set; }
    
        public XmlSchema GetSchema()
        {
            return null; // GetSchema should not be used.
        }
    
        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();
            bool isEmpty = reader.IsEmptyElement;
    
            reader.ReadStartElement();
            if (!isEmpty)
            {
                Value = reader.ReadString();
                reader.ReadEndElement();
            }
        }
        public void WriteXml(XmlWriter writer)
        {
            writer.WriteString(Value);
        }
    }
    

Serialization of a CustomString now yields <string-wrapper>Testing</string-wrapper>. I'll post some test code at the end of this post.

  1. Your deserialization is likely broken because the XmlSerializer doesn't know that the IEnumerable you marked serializable should actually be treated like a string.

And now... Forget what I just told you. Unless you have very specific formatting requirements you should not implement your own version of a string. The built-in formatter knows a lot more formatting tricks (http://www.w3.org/TR/xmlschema-2/#built-in-datatypes), so you should probably let it do it's job.

Serializing classes is where the attributes come in, though I recommend you switch to the WFC data contracts, which may sound scary at first but actually provides a whole lot more for a lot less code. Again, I'm not sure what you're trying to accomplish, but trust me you don't want to get into the habit of hand writing XML.

If you're up for it you might like dynamic objects and the ExpandoObject (http://www.codeproject.com/Tips/227139/Converting-XML-to-an-dynamic-object-using-ExpandoO). These eliminate types all together and allow you to create dictionaries, arrays, named properties, whatever, all on the fly!

Finally, easy on the generics! Deserializing generic classes is not a trivial task. Besides, you probably don't need to. If you want your own collections, try the System.Collections.ObjectModel namespace. You don't have to worry about maintaining lists and implementing interfaces, just what you're actually storing:

class DictionaryOfStringsAndObjects : KeyedCollection<string, object {...}
class CollectionOfStrings : Collection<string> {...}

Also, try to avoid partial classes unless an ORM or a large legacy class forces it on you. You shouldn't actually use them unless you're made to.

All the best, and to a future devoid of XML!

public class CustomSerializer
{
    public static void Test()
    {
        var obj = new CustomString {Value = "Random string!"};
        var serializer = new CustomSerializer();
        var xml = serializer.Serialize(obj);
        Console.WriteLine(xml);

        var obj2 = serializer.Deserialize<CustomString>(xml);
    }

    public string Serialize(object obj)
    {
        var serializer = new XmlSerializer(obj.GetType());
        using (var io = new StringWriter())
        {
            serializer.Serialize(io, obj);
            return io.ToString();
        }
    }

    public T Deserialize<T>(string xml)
    {
        var serializer = new XmlSerializer(typeof (T));
        using (var io = new StringReader(xml))
        {
            return (T)serializer.Deserialize(io);
        }
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Did you test that implementation of ReadXml()? My understanding is that it needs to read the element end (and maybe check for the element being empty): see codeproject.com/Articles/43237/… and stackoverflow.com/questions/279534/…
I did, works fine; from what I can tell the [XmlType] is responsible for initiating and finalizing, and as this is a really simple class, well..
Also, OP, please note that obj != obj2 whereas with strings this would be true.
Your ReadXml() has a problem in that subsequent XML elements are not read properly. See dotnetfiddle.net/jBC09w for a demo.
Turk, I'm curious, how many extension methods are we talking about haha? Also, you don't have to use XML to store things, especially if you control the library and client apps. You should at least consider binary formatting (code.google.com/p/protobuf-net) or JSON, the latter being devoid of user types making it much much easier to parse.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.