1

Hi I need to deserialize XML into object of Parameters class.

I defined this generic function that does the serialization:

public static T Deserialize<T>(string xml) where T : class
{
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    using (StringReader reader = new StringReader(xml))
    {
        return (T)serializer.Deserialize(reader);
    }
}

XML string I want to deserialize:

<Parameters>
    <UserProfileState>0</UserProfileState>
    <Parameter>
        <Name>Country</Name>
        <Type>String</Type>
        <Nullable>False</Nullable>
        <AllowBlank>False</AllowBlank>
        <MultiValue>True</MultiValue>
        <UsedInQuery>True</UsedInQuery>
        <State>MissingValidValue</State>
        <Prompt>Country</Prompt>
        <DynamicPrompt>False</DynamicPrompt>
        <PromptUser>True</PromptUser>
        <DynamicValidValues>True</DynamicValidValues>
        <DynamicDefaultValue>True</DynamicDefaultValue>
    </Parameter>
    <Parameter>
        <Name>City</Name>
        <Type>String</Type>
        <Nullable>False</Nullable>
        <AllowBlank>False</AllowBlank>
        <MultiValue>True</MultiValue>
        <UsedInQuery>True</UsedInQuery>
        <State>MissingValidValue</State>
        <Prompt>City</Prompt>
        <DynamicPrompt>False</DynamicPrompt>
        <PromptUser>True</PromptUser>
        <Dependencies>
            <Dependency>Country</Dependency>
        </Dependencies>
        <DynamicValidValues>True</DynamicValidValues>
        <DynamicDefaultValue>True</DynamicDefaultValue>
    </Parameter>
</Parameters>

Type (class) I want to deserialize my XML into:

[Serializable, XmlRoot(ElementName = "Parameters")]
public class ParametersModel
{
    [XmlElement(ElementName = "UserProfileState")]
    public int UserProfileState { get; set; }
    [XmlArray(ElementName = "Parameter")]
    public List<ParameterModel> Parameter { get; set; }
}

[Serializable]
public class ParameterModel
{
    [XmlElement(ElementName = "Name")]
    public string Name { get; set; }
    [XmlElement(ElementName = "Type")]
    public string Type { get; set; }
    [XmlElement(ElementName = "Nullable")]
    public bool Nullable { get; set; }
    [XmlElement(ElementName = "AllowBlank")]
    public bool AllowBlank { get; set; }
    [XmlElement(ElementName = "MultiValue")]
    public bool MultiValue { get; set; }
    [XmlElement(ElementName = "UsedInQuery")]
    public bool UsedInQuery { get; set; }
    [XmlElement(ElementName = "State")]
    public string State { get; set; }
    [XmlElement(ElementName = "Prompt")]
    public string Prompt { get; set; }
    [XmlElement(ElementName = "DynamicPrompt")]
    public bool DynamicPrompt { get; set; }
    [XmlElement(ElementName = "PromptUser")]
    public bool PromptUser { get; set; }
    [XmlElement(ElementName = "DynamicValidValues")]
    public bool DynamicValidValues { get; set; }
    [XmlElement(ElementName = "DynamicDefaultValue")]
    public bool DynamicDefaultValue { get; set; }

}

I call my Deserialize function from code, passing this XML:

var parameters = Utility.Deserialize<ParametersModel>(xml);

Now I dont get an exception, but Parameter collection is Empty

enter image description here

What is wrong?

Thank you

UPDATE1

<?xml version="1.0" encoding="utf-16"?>
<Parameters xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <UserProfileState>0</UserProfileState>
  <Parameters>
    <Parameter />
    <Parameter />
  </Parameters>
</Parameters>

There is an extra node under UserProfileState.

Do you guys know why I get this extra node, some attribute is defined incorrectly?

UPDATE2

I have modified the target object class as was suggested into this, and the deserialization now works fine, with the only problem is that I had to change type from bool to string for some properties... now I need to figure out how to keep bool properties and still have conversion working....

[Serializable]
[XmlRoot(ElementName = "Parameters")]
public class ParametersModel
{
    [XmlElement(ElementName = "UserProfileState")]
    public int UserProfileState { get; set; }
    [XmlElement(ElementName = "Parameter")]
    public List<ParameterModel> Parameters { get; set; }
}

[Serializable]
public class ParameterModel
{
    [XmlElement(ElementName = "Name")]
    public string Name { get; set; }
    [XmlElement(ElementName = "Type")]
    public string Type { get; set; }
    [XmlElement(ElementName = "Nullable")]
    public string Nullable { get; set; }
    [XmlElement(ElementName = "AllowBlank")]
    public string AllowBlank { get; set; }
    [XmlElement(ElementName = "MultiValue")]
    public string MultiValue { get; set; }
    [XmlElement(ElementName = "UsedInQuery")]
    public string UsedInQuery { get; set; }
    [XmlElement(ElementName = "State")]
    public string State { get; set; }
    [XmlElement(ElementName = "Prompt")]
    public string Prompt { get; set; }
    [XmlElement(ElementName = "DynamicPrompt")]
    public string DynamicPrompt { get; set; }
    [XmlElement(ElementName = "PromptUser")]
    public string PromptUser { get; set; }
    [XmlElement(ElementName = "DynamicValidValues")]
    public string DynamicValidValues { get; set; }
    [XmlElement(ElementName = "DynamicDefaultValue")]
    public string DynamicDefaultValue { get; set; }
}

UPDATE3

For boolean properties I implemented suggested workaround, "Nullable" property is of type bool, so I had to define additional property that will translate string to bool and bool to string automatically. Thank you guys for this suggestion.

[Serializable]
public class ParameterModel
{
    [XmlElement(ElementName = "Name")]
    public string Name { get; set; }
    [XmlElement(ElementName = "Type")]
    public string Type { get; set; }

    [XmlIgnore]
    public bool Nullable { get; set; }

    [XmlElement("Nullable")]
    public string NullableSerialize
    {
        get { return this.Nullable.ToString(); }
        set { this.Nullable = Convert.ToBoolean(value); }
    }

    [XmlElement(ElementName = "AllowBlank")]
    public string AllowBlank { get; set; }
    [XmlElement(ElementName = "MultiValue")]
    public string MultiValue { get; set; }
    [XmlElement(ElementName = "UsedInQuery")]
    public string UsedInQuery { get; set; }
    [XmlElement(ElementName = "State")]
    public string State { get; set; }
    [XmlElement(ElementName = "Prompt")]
    public string Prompt { get; set; }
    [XmlElement(ElementName = "DynamicPrompt")]
    public string DynamicPrompt { get; set; }
    [XmlElement(ElementName = "PromptUser")]
    public string PromptUser { get; set; }
    [XmlElement(ElementName = "DynamicValidValues")]
    public string DynamicValidValues { get; set; }
    [XmlElement(ElementName = "DynamicDefaultValue")]
    public string DynamicDefaultValue { get; set; }
}
Maksim Simkin
  • 9,561
  • 4
  • 36
  • 49
monstro
  • 6,254
  • 10
  • 65
  • 111
  • 3
    The message is right there; ` was not expected.` – bixarrio Jan 18 '17 at 15:47
  • @Bixarrio: Except that that string doesn't appear in the xml. There is a `` element but it doesn't have that xmlns thing on it so I am not sure the message is as helpful as you seem to think. I would imagine OP is thinking "Well that is the start of my XML. What is it expecting if not that?" – Chris Jan 18 '17 at 15:52
  • bixarrio, I know, what does it mean ? – monstro Jan 18 '17 at 15:53
  • @Dan: Yup. You are almost certainly right. My point though was that comments suggesting the answer to the question is obvious are, in my view, uncharitable. The error message really isn't as obvious as bixarrio is suggesting. – Chris Jan 18 '17 at 15:58
  • Please show the code which sets up variable 'xml' in GetReportParameters() –  Jan 18 '17 at 16:15
  • In regard to the update about the extra element; your class is defined that way. The list goes into an element `` and each item will be a `Parameter` element. To 'flatten' it, see [here](http://stackoverflow.com/questions/11731947/xml-serialization-of-list) – bixarrio Jan 18 '17 at 17:03
  • 1
    You should not be continually updating your question with new questions. If you have new questions hit the ask question button and ask the new questions seprately. You can always reference back to this one if you feel you need to. – Chris Jan 18 '17 at 20:27

5 Answers5

3

You could serialize your xml if you define your classes this way:

[Serializable]
[XmlRoot("Parameters")]
public class ParametersModel
{
    [XmlElement]
    public int UserProfileState { get; set; }   

    [XmlElement("Parameter")]   
    public List<ParameterModel> Parameters { get; set; }
}

but it won't work, becasue XmlSerialize could not parse your False/True to bool values. So you should either change all your bool properties to string or consider to use this from microsoft suggested workaround: https://blogs.msdn.microsoft.com/helloworld/2009/04/03/workaround-to-deserialize-true-false-using-xmlserializer/

Maksim Simkin
  • 9,561
  • 4
  • 36
  • 49
  • I changed bool to string, but the problem didnt go away – monstro Jan 18 '17 at 16:38
  • 1
    Have you made other chabges too ? I managed to deserialize your xml this way – Maksim Simkin Jan 18 '17 at 16:40
  • Dan, I tried to Serialize and object into XML, please take a look at the bottom of my post, I added what xml I get, there is additional node appears there, what am I doing wrong? – monstro Jan 18 '17 at 17:00
  • @monstro Did you change the `[XmlArray(ElementName = "Parameter")]` in your class to `[XmlElement(ElementName = "Parameter")]`? That should solve the issue with the extra element – bixarrio Jan 18 '17 at 17:13
  • Maksim, you are right, "True" / "False" are not casted to bools, so I tried string and it worked! But siince I prefer bool, need to find how to cast to bool when deserializing – monstro Jan 18 '17 at 17:13
  • bixarrio, yes I did, the post is updated (working model is at the bottom of the post), I got it working now, the only problem is that I had to change "bool" type for all Boolean properties to "string", it cannot perform deserialization if the type is bool :(( – monstro Jan 18 '17 at 17:20
  • @monstro take a look on article from msdn blog in my answer – Maksim Simkin Jan 18 '17 at 17:45
3

I think the following should get you pretty close. First, your top-level class

[XmlRoot(nameof(Parameters))]
public sealed class ParametersModel
{
    public int UserProfileState { get; set; }

    [XmlElement("Parameter")]
    public List<ParameterModel> Parameters { get; set; }
}

Your ParameterModel class is then

public sealed class ParameterModel
{
    public string Name { get; set; }
    public string Type { get; set; }
    public BooleanAsString Nullable { get; set; }
    public BooleanAsString AllowBlank { get; set; }
    public BooleanAsString MultiValue { get; set; }
    public BooleanAsString UsedInQuery { get; set; }
    public string State { get; set; }
    public string Prompt { get; set; }
    public BooleanAsString DynamicPrompt { get; set; }
    public BooleanAsString PromptUser { get; set; }
    public List<Dependency> Dependencies { get; set; }
    public BooleanAsString DynamicValidValues { get; set; }
    public BooleanAsString DynamicDefaultValue { get; set; }
}

public sealed class Dependency
{
    [XmlText]
    public string Value { get; set; }
}

I've done the bool/string stuff in a utility class

// https://blogs.msdn.microsoft.com/helloworld/2009/04/03/workaround-to-deserialize-true-false-using-xmlserializer/
public struct BooleanAsString
{
    public BooleanAsString(bool value = default(bool))
    {
        StringValue = null;
        Value = value;
    }
    public static implicit operator BooleanAsString(bool value)
    {
        return new BooleanAsString(value);
    }
    public static implicit operator bool(BooleanAsString value)
    {
        return value.Value;
    }

    [XmlIgnore]
    public bool Value
    {
        get { return Boolean.Parse(StringValue); }
        set { StringValue = value ? "True" : "False"; }
    }

    [XmlText]
    public string StringValue { get; set; }
}

Test code is:

    static string XmlSerialize(Object o)
    {
        var serializer = new XmlSerializer(o.GetType());
        using (var writer = new StringWriter())
        {
            serializer.Serialize(writer, o);
            return writer.ToString();
        }
    }

    static void Main(string[] args)
    {
        var parameters = new ParametersModel { UserProfileState = 0, Parameters = new List<ParameterModel>() };
        parameters.Parameters.Add(new ParameterModel
        { Name = "County", Type = "String", Nullable = false, AllowBlank = false, MultiValue = true, UsedInQuery = true, State = "MissingValidValue", Prompt = "County", DynamicPrompt = false, PromptUser = true, DynamicValidValues = true, DynamicDefaultValue = true});
        var pm = new ParameterModel
        { Name = "City", Type = "String", Nullable = false, AllowBlank = false, MultiValue = true, UsedInQuery = true, State = "MissingValidValue", Prompt = "City", DynamicPrompt = false, PromptUser = true, DynamicValidValues = true, DynamicDefaultValue = true };
        pm.Dependencies = new List<Dependency>() { new Dependency{ Value = "Country" } };
        parameters.Parameters.Add(pm);

        var s = XmlSerialize(parameters);
    }

which generates the following XML

<?xml version="1.0" encoding="utf-16"?>
<Parameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <UserProfileState>0</UserProfileState>
  <Parameter>
    <Name>County</Name>
    <Type>String</Type>
    <Nullable>False</Nullable>
    <AllowBlank>False</AllowBlank>
    <MultiValue>True</MultiValue>
    <UsedInQuery>True</UsedInQuery>
    <State>MissingValidValue</State>
    <Prompt>County</Prompt>
    <DynamicPrompt>False</DynamicPrompt>
    <PromptUser>True</PromptUser>
    <DynamicValidValues>True</DynamicValidValues>
    <DynamicDefaultValue>True</DynamicDefaultValue>
  </Parameter>
  <Parameter>
    <Name>City</Name>
    <Type>String</Type>
    <Nullable>False</Nullable>
    <AllowBlank>False</AllowBlank>
    <MultiValue>True</MultiValue>
    <UsedInQuery>True</UsedInQuery>
    <State>MissingValidValue</State>
    <Prompt>City</Prompt>
    <DynamicPrompt>False</DynamicPrompt>
    <PromptUser>True</PromptUser>
    <Dependencies>
      <Dependency>Country</Dependency>
    </Dependencies>
    <DynamicValidValues>True</DynamicValidValues>
    <DynamicDefaultValue>True</DynamicDefaultValue>
  </Parameter>
</Parameters>
Ðаn
  • 10,934
  • 11
  • 59
  • 95
0

Your problem is that you have told it to deserialize to an object of type ParametersModel. You have given no hints for names of elements so it is using the class name as the element name. If you were to change the xml so that the root element was ParametersModel then you would find it would no longer error. It would however also not return the results you expected because of further similar problems elsewhere.

In comments Dan suggested that it is easiest to start with serialization and he is correct. If you try to create an instance of your ParametersModel object and then serialize that then you will see the names and structures that your model suggests. If you then tweak your models and their attributes until the output matches what you are expecting you are then in a good position to correctly deserialize xml documents.

Chris
  • 27,210
  • 6
  • 71
  • 92
-1

You're probably missing the XML header tag, something like

<?xml version="1.0" encoding="utf-8"?>
Eduard Malakhov
  • 1,095
  • 4
  • 13
  • 25
  • The question has all that you need to recreate the problem so there is no need to guess at solutions and you can easily verify any answers. As it turns out adding this to the beginning of the XML makes no difference to the error message given. – Chris Jan 18 '17 at 15:59
-1

Change the XmlRootAttribute declaration, on ParametersModel, from:

[XmlRoot]

to:

[XmlRoot(ElementName = "Parameters")]

Full posting:

class Program
{
    static void Main(string[] args)
    {
        var s = @"<Parameters>
<UserProfileState>1</UserProfileState>
<Parameter>
    <Name>Country</Name>
    <Type>String</Type>
    <Nullable>false</Nullable>
    <AllowBlank>false</AllowBlank>
    <MultiValue>true</MultiValue>
    <UsedInQuery>true</UsedInQuery>
    <State>MissingValidValue</State>
    <Prompt>Country</Prompt>
    <DynamicPrompt>false</DynamicPrompt>
    <PromptUser>true</PromptUser>
    <DynamicValidValues>true</DynamicValidValues>
    <DynamicDefaultValue>true</DynamicDefaultValue>
</Parameter>
<Parameter>
    <Name>City</Name>
    <Type>String</Type>
    <Nullable>false</Nullable>
    <AllowBlank>false</AllowBlank>
    <MultiValue>true</MultiValue>
    <UsedInQuery>true</UsedInQuery>
    <State>MissingValidValue</State>
    <Prompt>City</Prompt>
    <DynamicPrompt>false</DynamicPrompt>
    <PromptUser>true</PromptUser>
    <Dependencies>
        <Dependency>Country</Dependency>
    </Dependencies>
    <DynamicValidValues>true</DynamicValidValues>
    <DynamicDefaultValue>true</DynamicDefaultValue>
</Parameter>
</Parameters>";

        var serializer = new XmlSerializer(typeof(ParametersModel));
        using (var reader = new StringReader(s))
        {
            var result = (ParametersModel)serializer.Deserialize(reader);
            var sb = new StringBuilder();
            using (var writer = new StringWriter(sb))
            {
                serializer.Serialize(writer, result);
            }
        }

    }
}

[Serializable]
[XmlRoot(ElementName ="Parameters")]
public class ParametersModel
{
    [XmlElement]
    public int UserProfileState { get; set; }

    [XmlElement("Parameter")]
    public List<ParameterModel> Parameters { get; set; }
}

[Serializable]
public class ParameterModel
{
    [XmlElement]
    public string Name { get; set; }
    [XmlElement]
    public string Type { get; set; }
    [XmlElement]
    public bool Nullable { get; set; }
    [XmlElement]
    public bool AllowBlank { get; set; }
    [XmlElement]
    public bool MultiValue { get; set; }
    [XmlElement]
    public bool UsedInQuery { get; set; }
    [XmlElement]
    public string State { get; set; }
    [XmlElement]
    public string Prompt { get; set; }
    [XmlElement]
    public bool DynamicPrompt { get; set; }
    [XmlElement]
    public bool PromptUser { get; set; }
    [XmlElement]
    public bool DynamicValidValues { get; set; }
    [XmlElement]
    public bool DynamicDefaultValue { get; set; }

}
Eric
  • 1,737
  • 1
  • 13
  • 17
  • Without the extra metadata, the serializer was looking for a root level element of "ParametersModel". – Eric Jan 18 '17 at 16:10
  • I updated the model, the exception is gone, but now the Parameter collection (array) returned is empty, please see the updated post with a screenshot – monstro Jan 18 '17 at 16:14
  • Problem #2: Change the XmlElementAttribute on Parameters to [XmlElement("Parameter") – Eric Jan 18 '17 at 16:18
  • Problem #3: Change the True/False values to true/false in your XML – Eric Jan 18 '17 at 16:20
  • Added a couple lines to demonstrating re-serialization as well – Eric Jan 18 '17 at 16:28