Wednesday, April 20, 2011

Converting from String to <T>

I really should be able to get this, but I'm just to the point where I think it'd be easier to ask.

In the C# function:

public static T GetValue<T>(String value) where T:new()
{
   //Magic happens here
}

What's a good implementation for the magic? The idea behind this is that I have xml to parse and the desired values are often primitives (bool, int, string, etc.) and this is the perfect place to use generics... but a simple solution is eluding me at the moment.

btw, here's a sample of the xml I'd need to parse

<Items>
    <item>
     <ItemType>PIANO</ItemType>
     <Name>A Yamaha piano</Name>
     <properties>
      <allowUpdates>false</allowUpdates>
      <allowCopy>true</allowCopy>
     </properties> 
    </item>
    <item>
     <ItemType>PIANO_BENCH</ItemType>
     <Name>A black piano bench</Name>
     <properties>
      <allowUpdates>true</allowUpdates>
      <allowCopy>false</allowCopy>
      <url>www.yamaha.com</url>
     </properties>
    </item>
    <item>
     <ItemType>DESK_LAMP</ItemType>
     <Name>A Verilux desk lamp</Name>
     <properties>
      <allowUpdates>true</allowUpdates>
      <allowCopy>true</allowCopy>
      <quantity>2</quantity>
     </properties>
    </item>
</Items>
From stackoverflow
  • I would suggest instead of trying to parse XML yourself, you try to create classes that would deserialize from the XML into the classes. I would strongly recommend following bendewey's answer.

    But if you cannot do this, there is hope. You can use Convert.ChangeType.

    public static T GetValue<T>(String value)
    {
      return (T)Convert.ChangeType(value, typeof(T));
    }
    

    And use like so

    GetValue<int>("12"); // = 2
    GetValue<DateTime>("12/12/98");
    
    bendewey : +1 for XML Serialization to POCO
    jfar : I'll see your +1 and raise you another +1. Although I'm confused by its elegance this smells like using generics for generics sake.
    Jimmy : Lets argue about the aesthetics of GetValue() vs (int)GetValue() :P
    Saint Domino : Serialization is out of the question for the intended use of this function. We considered Xml <-> poco at first, but it kind of bogs down the app when the xml gets to the >1Mb range. Thus xpath is the way to go.
  • You can start with something roughly like this:

    TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
    if (converter != null)
    {
       return (T)converter.ConvertFrom(value);
    }
    

    If you have to parse attributes that are special types, like colors or culture strings or whatnot, you will of course have to build special cases into the above. But this will handle most of your primitive types.

    Marc Gravell : if you use GetConverter(typeof(T)) you don't need the t variable or the new()
    womp : Yeah I roughed it out really quick. Maybe I'll give it an edit - thanks.
  • For this to work correctly, your generic method is going to have to delegate its actual work to a dedicated class.

    Something like

    private Dictionary<System.Type, IDeserializer> _Deserializers;
        public static T GetValue<T>(String value) where T:new()
        {
           return _Deserializers[typeof(T)].GetValue(value) as T;
        }
    

    where _Deserializers is some kind of dictionary where you register your classes. (obviously, some checking would be required to ensure a deserializer has been registered in the dictionary).

    (In that case the where T:new() is useless because your method does not need to create any object.

    Samuel : You cannot use the as operator on generic types without the where class restriction.
    Denis Troller : woops, didn't know that one. You can still do a regular Cast I presume ?
  • If you decide to go the route of serialization to POCO (Plain old CLR Object), then there are few tools that can help you generate your objects.

    • You can use xsd.exe to generate a .cs file based on your XML Definition
    • There is a new feature in the WCF REST Starter Kit Preview 2, called Paste as Html. This feature is really cool and lets you take a block of HTML thats in your clipboard, then when you paste it into a cs file it automatically converts the xml to the CLR object for serialization.
    Saint Domino : Serialization is out of the question for the intended use of this function. We considered Xml <-> poco at first, but it kind of bogs down the app when the xml gets to the >1Mb range. The boss has said xpath is the way to go
    bendewey : LINQ to XML is a faster than xpath.
    bendewey : See http://en.wikipedia.org/wiki/XML#Processing_files, XPath uses DOM, Linq to XML uses 'pull parsing'
    Saint Domino : remember, some of us are going to be stuck in the .net 2.0 world for a while... :-)
    bendewey : Ouch, I'm terribly sorry to hear that.
  • again with the caveat that doing this is probably a bad idea:

    class Item 
    {
        public string ItemType { get; set; }
        public string Name { get; set; }
    }
    
    public static T GetValue<T>(string xml) where T : new()
    {
        var omgwtf = Activator.CreateInstance<T>();
        var xmlElement = XElement.Parse(xml);
        foreach (var child in xmlElement.Descendants())
        {
            var property = omgwtf.GetType().GetProperty(child.Name.LocalName);
            if (property != null) 
                property.SetValue(omgwtf, child.Value, null);
        }
        return omgwtf;
    }
    

    test run:

    static void Main(string[] args)
    {
        Item piano = GetValue<Item>(@"
            <Item>
                <ItemType />
                <Name>A Yamaha Piano</Name>
                <Moose>asdf</Moose>
            </Item>");
    }
    

0 comments:

Post a Comment