Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize xml into super class object with C#

I'm creating a program that allow user define formulas with on 4 basic operation: add, subtract, divide, multiple using XML. Let's take an example: User want to define formula like (a + b) x (c + d). The format of the xml as following:

EDIT I had implement this

EDIT Solve. Many thanks to Yaniv's suggestion. My solution as follow:

<xPlugins>
  <xPlugin>
    <Multiple>
      <Add>
        <Operator>
          <value>1</value>
        </Operator>
        <Operator>
          <value>2</value>
        </Operator>
      </Add>
      <Add>
        <Operator>
          <value>3</value>
        </Operator>
        <Operator>
          <value>4</value>
        </Operator>
      </Add>
    </Multiple>
  </xPlugin>
</xPlugins>

classes

//root element
public class xPlugins
{
    [XmlElement("xPlugin", typeof(xPlugin))]
    public xPlugin[] Plugin { get; set; }
}

public class xPlugin
{
    [XmlElement("Multiple", typeof(Multiple))]
    [XmlElement("Add", typeof(Add))]
    [XmlElement("Subtract", typeof(Divide))]
    [XmlElement("Divide", typeof(Divide))]
    [XmlElement("Operator", typeof(Operand))]
    public Calculator calculator { get; set; }        
}

//Deseirialize ultility
static class readXML
{        
    public static void getObject(ref xPlugins plugins)
    {
        try
        {
            List<Type> type = new List<Type>();
            type.Add(typeof(Add));
            type.Add(typeof(Minus));
            type.Add(typeof(Multiple));
            type.Add(typeof(Subtract));
            type.Add(typeof(Operator));

            XmlSerializer xml = new XmlSerializer(typeof(xPlugin), type.ToArray());

            FileStream fs = new FileStream("test.xml", FileMode.Open);

            plugins = (xPlugins)xml.Deserialize(fs);
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

public abstract class Calculator
{
    [XmlElement("Multiple", typeof(Multiple))]
    [XmlElement("Add", typeof(Add))]
    [XmlElement("Subtract", typeof(Subtract))]
    [XmlElement("Divide", typeof(Divide))]
    [XmlElement("Operator", typeof(Operand))]
    public List<Calculator> calculators{ get; set; }
    public virtual int Calculate()
    {
        return 0;
    }
}

public class Operator : Calculator
{
    public int value { get; set; }

    public Operator() { }

    public override int Calculate()
    {
        return value;
    }
}

public class Add : Calculator
{
    public Add() { }

    public override int Calculate()
    {         
        List<int> value = new List<int>();

        foreach (Calculator calculator in calculators)
        {
            value.Add(calculator.Calculate());
        }

        return value.Sum();
    }
}

public class Minus : Calculator
{
    public Minus() { }

    public override int Calculate()
    {
        int value = calculators[0].Calculate();

        for (int i = 1; i < calculators.Count; i++)
        {
            value -= calculators[i].Calculate();
        }

        return value;
    }
}

public class Divide: Calculator
{ 
    public Divide() { }

    public override int Calculate()
    {
        int value = calculators[0].Calculate();

        for (int i = 1; i < calculators.Count; i++)
        {
            value /= calculators[i].Calculate();
        }

        return value;
    }
}

public class Multiple : Calculator
{
    public Multiple() { }

    public override int Calculate()
    {
        int value = calculators[0].Calculate();

        for (int i = 1; i < calculators.Count; i++)
        {
            value *= calculators[i].Calculate();
        }

        return value;
    }
}

//running test
private void button1_Click(object sender, EventArgs e)
    {
        readXML.getObject(ref this.plugins);

        foreach (Calculator plugin in plugins.calculators)
        {
            plugin.Calculate();
        }
    }

I just have to decorate Calculator property with:

[XmlElement("Multiple", typeof(Multiple))]
[XmlElement("Add", typeof(Add))]
[XmlElement("Subtract", typeof(Divide))]
[XmlElement("Divide", typeof(Divide))]
[XmlElement("Operator", typeof(Operand))]
like image 404
Doan Cuong Avatar asked Jun 25 '13 08:06

Doan Cuong


1 Answers

I am guessing you want to use XmlSerializer. If you need a "polymorphic" deserialization you can pass a list of types that the serializer should know about (this works if they all inherit from the same base class but not from interface).

Example:

List<Type> extraTypes = new List<Type>();
extraTypes.Add(typeof(multiple));
extraTypes.Add(typeof(add));
extraTypes.Add(typeof(substract));
extraTypes.Add(typeof(divide));
var ser = new XmlSerializer(typeof(Foo), extraTypes.ToArray());

It's explained here: Serializing and restoring an unknown class

But there is another problem that in your XML your operand can hold two different types: an operation or an parameter (a, b, c, d) and you cannot represent it in your class.

Something that I usually see is this (I implemented only the add operation, and I am assuming the expression is numeric):

public class Expression
{
  public virtual int Evaluate()
  {
  }
}

public class Add : Expression
{
  Expression _left;
  Expression _right;

  public Add(Expression left, Expression right)
  {
    _left = left;
    _right = right;
  }

  override int Evalute()
  {
    return _left.Evalute() + _right.Evalute();
  }
}

public class Parameter : Expression
{
  public int Value{get;set;}

  public Parameter(string name)
  {
    // Use the name however you need.
  }

  override int Evalute()
  {
    return Value;
  }
}

This way you have only one base class so everything is simpler. If that make sense I guess it won't be hard to deserialize it.

EDIT: If the base class is Calculator (instead of Expression) the XML will look like this:

<Calculator xsi:type="Multiple">
  <calculators>
    <Calculator xsi:type="Add">
      <calculators>
        <Calculator xsi:type="Operator">
          <value>12</value>
        </Calculator>
      </calculators>
    </Calculator>
  </calculators>
</Calculator>

I have created a simple calculator object and serialized it and that's what I got. If you will deserialize it you will get a calculator that will return 12.

Maybe you can use XmlAttributes to change the names of the elements in the XML or in the worst case write your own deserializer.

like image 103
Yaniv Avatar answered Oct 03 '22 06:10

Yaniv