13

Say we have a struct that it's data is provided by un-managed byte array using Marshal.PtrToStructure.

The C# struct layout:

[StructLayout(LayoutKind.Sequential, Size = 128, CharSet = CharSet.Ansi, Pack = 1)]
public struct MNG_Y_Params
{
    public byte Number;
    public byte Version;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
    public byte[] OliNumber;
    public byte InterfaceType;
}

The byte array represent a (ascii) string in the un-managed code.

This struct is a member of another struct (that has some other members):

public struct MyData
{
    public int ID;
    public StructType structType;
    [XmlElement(ElementName="MNG_Y_Params")]
    public MNG_Y_Params y_params;
    [XmlElement(ElementName = "SimpleStruct2")]
    public SimpleStruct2 ss2;
};

So we also have this support code

public class XMLIgnore
{
    static public XmlSerializer customserialiser(MyData d)
    {
        XmlAttributes attrs = new XmlAttributes();
        attrs.XmlIgnore = true;
        XmlAttributeOverrides xmlOveride = new XmlAttributeOverrides();
        switch (d.structType)
        {
            case StructType.ST_1:
                xmlOveride.Add(typeof(MyData), "ss2", attrs);
                break;
            case StructType.ST_2:
                xmlOveride.Add(typeof(MyData), "y_params", attrs);
                break;
            default:
                break;
        }
        return new XmlSerializer(typeof(MyData), xmlOveride);
    }
}

and the save method

    static void SaveToXml(object obj, string fileName, XmlSerializer writer)
    {
        //XmlSerializer writer = new XmlSerializer(obj.GetType());
        using (StreamWriter file = new StreamWriter(fileName))
        {
            writer.Serialize(file, obj);
        }
    }

For the example we'd just generate some data.

        MNG_Y_Params yParams = new MNG_Y_Params();
        yParams.Version = 1;
        yParams.InterfaceType = 15;
        yParams.Number = 35;
        ASCIIEncoding enc = new ASCIIEncoding();
        yParams.OliNumber = enc.GetBytes("#1");

        MyData md1 = new MyData();
        md1.ID = 1;
        md1.structType = StructType.ST_1;
        md1.y_params = yParams;

        XmlSerializer writer = XMLIgnore.customserialiser(md1);
        SaveToXml(md1, @"C:\temp\dataOne.xml", writer);

Expected XML:

<?xml version="1.0" encoding="utf-8"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ID>1</ID>
  <structType>ST_1</structType>
  <MNG_Y_Params>
    <Number>35</Number>
    <Version>1</Version>
    <OliNumber>#1</OliNumber>
    <InterfaceType>15</InterfaceType>
  </MNG_Y_Params>
</MyData>

Result XML:

<?xml version="1.0" encoding="utf-8"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ID>1</ID>
  <structType>ST_1</structType>
  <MNG_Y_Params>
    <Number>35</Number>
    <Version>1</Version>
    <OliNumber>IzE=</OliNumber>
    <InterfaceType>15</InterfaceType>
  </MNG_Y_Params>
</MyData>

Please focus on the byte array member (OliNumber) result.

Is there an attribute that we could use here? What am I missing?

Thanks for your time and help.

Ilan

For reference

4
  • When you deserialize, is you data in OliNumber different from before the serialization? The value in the XML may be good, maybe it's only the format that XMLSerializer use for byte array. Commented Mar 21, 2011 at 13:13
  • @Kipotlov Thanks for your comment. Deserializing works fine, however the point is to represent in the XML the data from the un-managed code, where the byte array represent a (ascii) string. Commented Mar 21, 2011 at 14:29
  • The serializer treats byte[] as binary data not a string, so it uses transfer encoding (base64) to allow arbitrary binary data to pass through XML. Please read the spec, XML does not allow most non-printing characters in content. If you are sending a string, call it String. Commented Feb 12, 2016 at 10:27
  • I realize this is old, but I believe if you change your encoding...<?xml version="1.0" encoding="utf-8"?> to utf-16, it may work. You do not discuss the base character set, so you maybe hitting an issue there in the XML converter. I had this issue handling translations from other language sets. utf-16 picked up dual byte characters where utf-8 did not. Commented Jan 18, 2018 at 21:59

3 Answers 3

8

The XmlSerializer by default will encode the byte arrays using base 64 encoding. If you use this site and paste in IzE=, and decode it, the result will be #1. You can change the encoding by setting the XmlElementAttribute.DataType. I'm not sure if [XmlElement(DataType = "string")] will work, but you can try it. Using [XmlElement(DataType = "hexBinary")] will generate the raw bytes.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, tried your suggestion got an exception when creating the customized 'XmlSerializer': "'string' is an invalid value for the XmlElementAttribute.DataType property. string cannot be converted to System.Byte[]". As for the other option "HexBinary" result was: '<OliNumber>2331</OliNumber>'
@IIan - Yeah thats about what I expected. I think if you want an string value output, you will have to implement IXmlSerializable and do it yourself.
1

I got this to work using the following:

  public class MySerializableClass
  {   
    [XmlIgnore]
    public string NaughtyXmlCharactersAsString { get; set; }

    [XmlElement(ElementName = "NaughtyXmlCharacters", DataType = "hexBinary")]
    public byte[] NaughtyXmlCharactersAsBytes
    {
        get { return Encoding.UTF8.GetBytes(NaughtyCharactersAsString ?? string.Empty); }
        set { NaughtyXmlCharactersAsString = Encoding.UTF8.GetString(value); }
    }

I would then only access the "AsString" version of the property.

Comments

0

This is how I did it:

public class MySerializableClass
{
    private string dummy;

    [XmlElement("NaughtyXmlCharacters")]
    public string NaughtyXmlCharactersAsString
    {
       get 
       {
           return BitConverter.ToString(NaughtyXmlCharacters);
       }
       set
       {
           // without this, the property is not serialized.
           dummy = value;
       }
    }

    [XmlIgnore]
    public byte[] NaughtyXmlCharacters
    {
        get;
        set;
    }
}

The bytes are then formatted as hexadecimal values and separated with a minus sign: 00-AF-B1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.