Search code examples
c#jsonxmlxmlserializerdatacontractjsonserializer

Why are arrays being shown as null after serializing using DataContractJsonSerializer?


There have been a number of questions about serializing and deserializing arrays using DataContractJsonSerializer (including one from me: How can I serialise a string array to JSON using DataContractJsonSerializer?) but none seem to answer the current problem that I'm having.

I am converting an XML string to a JSON string by deserializing the XML to a DataContract object and then serializing that object to JSON, using DataContractJsonSerializer. My approach is the same as I have used on a number of other objects, all of which are serializing to JSON perfectly, but I have an object in which an array property is always rendered as null after serialization.

The classes are defined as follows:-

[DataContract]
public class Order
{
    [DataMember(Name = "consignments")]
    [XmlElement("consignments")]
    public Consignment[] Consignments { get; set; }
}

[DataContract]
public class Consignment
{
    [DataMember(Name = "conh_id")]
    [XmlElement("conh_id")]
    public string ConsignmentHeaderId { get; set; }

    [DataMember(Name = "conh_status")]
    [XmlElement("conh_status")]
    public string ConsignmentHeaderStatus { get; set; }

    [DataMember(Name = "consignmententries")]
    [XmlElement("consignmententries")]
    public ConsignmentEntry[] ConsignmentEntries { get; set; }
}

The XML I'm using looks like this:-

<order>
  <consignments>
    <consignment>
      <conh_id>A19708176</conh_id>
      <conh_status>ACCEPTED</conh_status>
      <consignmententries>
        <consignmententry>
          <conl_lineNbr>10000</conl_lineNbr>
          <conl_sku>SEC01XXZBUXXX</conl_sku>
          <conl_original_qty>1</conl_original_qty>
        </consignmententry>
      </consignmententries>
    </consignment>
  </consignments>
</order>

The deserializing and serializing is done in the following methods:-

private object DeserialiseXml(string xml)
{
    var serialiser = new XmlSerializer(typeof(Order));
    var stringReader = new StringReader(xml);
    var result = serialiser.Deserialize(stringReader);

    return result;
}

private string SerialiseJson(object serialisable)
{
    using (MemoryStream stream = new MemoryStream())
    {
        var serialiser = new DataContractJsonSerializer(serialisable.GetType());
        serialiser.WriteObject(stream, serialisable);
        var json = Encoding.UTF8.GetString(stream.ToArray());

        return json;
    } 
}

When I test it, the Consignments property is always null in the resulting JSON.

"order": {
  "consignments": [
    {
      "conh_id": null,
      "conh_status": null,
      "consignmententries": null
    }
  ]
}

Debugging shows that this property is null in the object created after the deserialization step so the problem is in the XML deserialization rather than the JSON serialization.

What do I need to change on my object model to get the array converted properly?


Solution

  • Your problem is happening when you deserialize from XML rather than when you serialize to JSON. In your XML, the collections have been serialized with two levels: an outer container element and an element for each item:

      <consignments>
        <consignment>
           <!-- Consignment data -->
        </consignment>
      </consignments>
    

    And

      <consignmententries>
        <consignmententry>
           <!-- Entry data -->
        </consignmententry>
      </consignmententries>
    

    (For comparison, in the linked question the Labels XML collection has a single level). Thus you need to use [XmlArray(string name)] and [XmlArrayItem(string itemName)] to specify the outer and inner element names:

    [DataContract]
    [XmlRoot("order")]
    public class Order
    {
        [DataMember(Name = "consignments")]
        [XmlArray("consignments")]
        [XmlArrayItem("consignment")]
        public Consignment[] Consignments { get; set; }
    }
    
    [DataContract]
    public class Consignment
    {
        [DataMember(Name = "conh_id")]
        [XmlElement("conh_id")]
        public string ConsignmentHeaderId { get; set; }
    
        [DataMember(Name = "conh_status")]
        [XmlElement("conh_status")]
        public string ConsignmentHeaderStatus { get; set; }
    
        [DataMember(Name = "consignmententries")]
        [XmlArray("consignmententries")]
        [XmlArrayItem("consignmententry")]
        public ConsignmentEntry[] ConsignmentEntries { get; set; }
    }
    

    Conversely, adding the attribute [XmlElement] to a collection tells XmlSerializer that the collection should be serialized without the outer container element.

    You also need to add [XmlRoot("order")] to Order to specify the root element name.

    Now your XML will deserialize successfully.