Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Xml serialization, collection and root element

My app serializes objects in streams. Here is a sample of what I need :

<links>
  <link href="/users" rel="users" />
  <link href="/features" rel="features" />
</links>

In this case, the object is a collection of 'links' object.

-----------First version

At first I used the DataContractSerializer, however you cannot serialize members as attributes (source)

Here is the object :

[DataContract(Name="link")]
public class LinkV1
{
    [DataMember(Name="href")]
    public string Url { get; set; }

    [DataMember(Name="rel")]
    public string Relationship { get; set; }
}

And here is the result :

<ArrayOflink xmlns:i="...." xmlns="...">
  <link>
    <href>/users</href>
    <rel>users</rel>
  </link>
  <link>
    <href>/features</href>
    <rel>features</rel>
  </link>
</ArrayOflink>

----------- Second version

Ok, not quiet what I want, so I tried the classic XmlSerializer, but... oh nooo, you cannot specify the name of the root element & of the collection's elements if the root element is a collection...

Here is the code :

[XmlRoot("link")]
public class LinkV2
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

Here is the result :

<ArrayOfLinkV2>
  <LinkV2 href="/users" rel="users" />
  <LinkV2 href="/features" rel="features" />
  <LinkV2 href="/features/user/{keyUser}" rel="featuresByUser" />
</ArrayOfLinkV2>

----------- Third version

using XmlSerializer + a root element :

[XmlRoot("trick")]
public class TotallyUselessClass
{
    [XmlArray("links"), XmlArrayItem("link")]
    public List<LinkV2> Links { get; set; }
}

And its result :

 <trick>
  <links>
    <link href="/users" rel="users" />
    <link href="/features" rel="features" />
    <link href="/features/user/{keyUser}" rel="featuresByUser" />
  </links>
</trick>

Nice, but I don't want that root node !! I want my collection to be the root node.

Here are the contraints :

  • the serialization code is generic, it works with anything serializable
  • the inverse operation (deserialization) have to work too
  • I don't want to regex the result (I serialize directly in an output stream)

What are my solutions now :

  1. Coding my own XmlSerializer
  2. Trick XmlSerializer when it works with a collection (I tried, having it find a XmlRootElement and plurialize it to generate its own XmlRootAttribute, but that causes problem when deserializing + the items name still keeps the class name)

Any idea ?

What really bother me in that issue, is that what I want seems to be really really really simple...

like image 509
Mose Avatar asked Aug 02 '12 16:08

Mose


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

What is C language basics?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.


2 Answers

Ok, here is my final solution (hope it helps someone), that can serialize a plain array, List<>, HashSet<>, ...

To achieve this, we'll need to tell the serializer what root node to use, and it's kind of tricky...

1) Use 'XmlType' on the serializable object

[XmlType("link")]
public class LinkFinalVersion
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

2) Code a 'smart-root-detector-for-collection' method, that will return a XmlRootAttribute

private XmlRootAttribute XmlRootForCollection(Type type)
{
    XmlRootAttribute result = null;

    Type typeInner = null;
    if(type.IsGenericType)
    {
        var typeGeneric = type.GetGenericArguments()[0];
        var typeCollection = typeof (ICollection<>).MakeGenericType(typeGeneric);
        if(typeCollection.IsAssignableFrom(type))
            typeInner = typeGeneric;
    }
    else if(typeof (ICollection).IsAssignableFrom(type)
        && type.HasElementType)
    {
        typeInner = type.GetElementType();
    }

    // yeepeeh ! if we are working with a collection
    if(typeInner != null)
    {
        var attributes = typeInner.GetCustomAttributes(typeof (XmlTypeAttribute), true);
        if((attributes != null)
            && (attributes.Length > 0))
        {
            var typeName = (attributes[0] as XmlTypeAttribute).TypeName + 's';
            result = new XmlRootAttribute(typeName);
        }
    }
    return result;
}

3) Push that XmlRootAttribute into the serializer

// hack : get the XmlRootAttribute if the item is a collection
var root = XmlRootForCollection(type);
// create the serializer
var serializer = new XmlSerializer(type, root);

I told you it was tricky ;)


To improve this, you can :

A) Create a XmlTypeInCollectionAttribute to specify a custom root name (If the basic pluralization does not fit your need)

[XmlType("link")]
[XmlTypeInCollection("links")]
public class LinkFinalVersion
{
}

B) If possible, cache your XmlSerializer (in a static Dictionary for example).

In my testing, instanciating a XmlSerializer without the XmlRootAttributes takes 3ms. If you specify an XmlRootAttribute, it takes around 80ms (Just to have a custom root node name !)

like image 171
Mose Avatar answered Oct 22 '22 09:10

Mose


XmlSerializer should be able to do what you need, but it is highly dependent on the initial structure and setup. I use it in my own code to generate remarkably similar things.

public class Links<Link> : BaseArrayClass<Link> //use whatever base collection extension you actually need here
{
    //...stuff...//
}

public class Link
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

now, serializing the Links class should generate exactly what you are looking for.

The problem with XmlSerializer is when you give it generics, it responds with generics. List implemets Array somewhere in there and the serialized result will nearly always be ArrayOf<X>. To get around that you can name the property, or the class root. The closes to what you need is probably the Second Version from your examples. Im assuming you attempted direct serialization of an object List Links. That wouldn't work because you didn't specify the root node. Now, a similar approach can be found here. In this one they specify the XmlRootAttribute when declaring the serializer. yours would look like this:

XmlSerializer xs = new XmlSerializer(typeof(List<Link>), new XmlRootAttribute("Links"));
like image 41
Nevyn Avatar answered Oct 22 '22 10:10

Nevyn