3

How do you serialize the following

[XmlRoot("response")]
public class MyCollection<T>
{
    [XmlElement("person", Type = typeof(Person))]
    public List<T> entry;
    public int startIndex;
}

where T can be a class like

public class Person
{
    public string name;
}

into

<response>
  <startIndex>1</startIndex>
  <entry>
      <person>
         <name>meeee</name>
      </person>
  </entry>
  <entry>
      <person>
         <name>youuu</name>
      </person>
  </entry>
</response>

I have been playing with [XmlArray], [XmlArrayItem], and [XmlElement] and I can't seem to get the right combination. Arrrgghhh.

Update:

[XmlArray("entry")]
[XmlArrayItem("person", Type = typeof(Person))]
public List<T> entry;

gives me

<entry><person></person><person></person></entry>


[XmlElement("person", Type = typeof(Person))]
public List<T> entry;

gives me

<person></person><person></person>
John Saunders
  • 160,644
  • 26
  • 247
  • 397
sean
  • 11,164
  • 8
  • 48
  • 56
  • The generics are still going to be a problem... what is the T in closed type (only closed types can be serialized)? – Marc Gravell Jun 12 '09 at 07:52
  • I'm also not sure what this has to do with default values - perhaps re-title... is it really about nesting sub-objects? – Marc Gravell Jun 12 '09 at 08:18
  • An open generic type is List; a closed generic type is List. Without knowing the T, it is hard to understand the relationship between List and Person – Marc Gravell Jun 12 '09 at 08:18
  • @seanlinmt: If you're still interested, I was able to run the OpenSocial schema through XSD.EXE, and I have learned how to fill it in and serialize it. – John Saunders Jul 25 '09 at 23:34

3 Answers3

4

I can't see any obvious way of getting it to output those results without changing the classes radically... this might not be what you want, but by mirroring the desired output (not uncommon in DTOs) it gets the right result...

Otherwise, you might be looking at IXmlSerializable, which is a huge pain:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
[XmlRoot("response")]
public class MyResponse {
    public MyResponse() {
        Entries = new List<Entry>();
    }
    [XmlElement("startIndex", Order = 1)]
    public int StartIndex { get; set; }
    [XmlElement("entry", Order = 2)]
    public List<Entry> Entries { get; set; }
}
public class Entry {
    public Entry() { }
    public Entry(Person person) { Person = person; }
    [XmlElement("person")]
    public Person Person { get; set; }
    public static implicit operator Entry(Person person) {
        return person == null ? null : new Entry(person);
    }
    public static implicit operator Person(Entry entry) {
        return entry == null ? null : entry.Person;
    }
}
public class Person {
    [XmlElement("name")]
    public string Name { get; set; }
}
static class Program {
    static void Main() {
        MyResponse resp = new MyResponse();
        resp.StartIndex = 1;
        resp.Entries.Add(new Person { Name = "meeee" });
        resp.Entries.Add(new Person { Name = "youuu" });
        XmlSerializer ser = new XmlSerializer(resp.GetType());
        ser.Serialize(Console.Out, resp);
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • IXmlSerializable should be better than what I have currently, ie. serializing and deserializing via reflection only – sean Jun 12 '09 at 08:31
  • I don't really want to change the structure of the classes too much. I still have DataContract attributes for serializing to JSON. And the example given is a simplified version of the classes. I can decorate using certain XMLxxx and Dataxxx attributes at the same time right? This way I can have different formats when serializing to JSON or XML. – sean Jun 12 '09 at 08:45
  • Yes, but I'm not sure how that helps if `XmlSerializer` doesn't want to write things the way you want... – Marc Gravell Jun 12 '09 at 09:28
  • It seems that with IXmlSerializable, I will have to explicitly specify each and every field that I want to serialize :( – sean Jun 12 '09 at 09:31
  • The reason of the explicit format is due to http://www.opensocial.org/Technical-Resources/opensocial-spec-v09/REST-API.html – sean Jun 12 '09 at 09:32
  • That site lists the xsd; why can't you just run it through xsd.exe and let *it* deal with everything? – Marc Gravell Jun 12 '09 at 10:36
  • I did. I've even tried LinqToXSD. But it doesn't seem to work out properly. e.g. a string field comes out as IList instead of just string. But I could be doing something wrong. I've actually been looking into using a surrogate especially for handling Dictionaries. But the problem is that I have difficulties finding examples. – sean Jun 16 '09 at 13:31
  • You also should have asked here when you had a problem with that xSD. I'm trying it now, and it's a PITA, and may be demonstrating a bug in XML Serialization. It's a nasty XSD, made for humans and not machines (``) followed by a list of simpletype elements which are valid choices. If I were a serializer, I'd give up. – John Saunders Jul 25 '09 at 22:44
1

I've found a way to do this without using IXmlSerializable. Use the XmlDictionaryWriter for formatting necessary parts and for the rest just stick with the DataContractSerializer. I created an interface for MyCollection

public interface IMyCollection
{
    int getStartIndex();
    IList getEntry();
}

Therefore, MyCollection is no longer decorated with any XMLxxxx attributes. And the method to convert to string is as follows. Can it be improved?

public string ConvertToXML(object obj)
{
    MemoryStream ms = new MemoryStream();
    using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, true))
    {
         writer.WriteStartDocument();
         if (obj is IMyCollection)
         {
              IMyCollection collection = (IMyCollection)obj;
              writer.WriteStartElement("response");
              writer.WriteElementString("startIndex","0");
              var responses = collection.getEntry();
              foreach (var item in responses)
              {
                   writer.WriteStartElement("entry");
                   DataContractSerializer ser = new DataContractSerializer(item.GetType());                
                   ser.WriteObject(writer, item);
                   writer.WriteEndElement();
              }
              writer.WriteEndElement();
        }
        writer.Flush();
        return Encoding.UTF8.GetString(ms.ToArray());
}
sean
  • 11,164
  • 8
  • 48
  • 56
0

This sounds like a similar issue I was having.. I finally cracked it and posted everything on the question here..

XML Serialization and Inherited Types

Is that of any use?

Community
  • 1
  • 1
Rob Cooper
  • 28,567
  • 26
  • 103
  • 142
  • Sorry for the misleading title. It's fixed now. As for excluding default values. I use public DateTime? birthday { get; set; } [XmlIgnore] public bool birthdaySpecified { get { return birthday.HasValue; } } – sean Jun 12 '09 at 08:33
  • The xxxSpecified does the job ok for me so far... but the main problem is the nesting of sub objects. – sean Jun 12 '09 at 08:33
  • I have to be honest, I am unsure of where the code in my answer is failing you.. You can't get a clean solution out of the box just using XML Attributes, and as Mark [correctly] says, in cases like this implementing IXmlSerializable is a pain. Basically you need to roll your own little bit of serialization to handle the "generic" bit.. Have you reviewed the code in my answer? Is there anything I can help clarify? – Rob Cooper Jun 12 '09 at 11:10
  • Sorry Marc, just realised I incorrectly spelled your name. My apologies. – Rob Cooper Jun 12 '09 at 11:13
  • I wish I can get a clean solution out of the box. I mean my question seems quite straight forward right? So I assume to the solution should be quite straight forward instead of beating around the bush? – sean Jun 12 '09 at 16:15
  • You're giving me a haystack and I'm trying to find the needle. – sean Jun 12 '09 at 16:16