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?
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.
Related
I'm getting XML in following format:
<Order>
<OrderData>
<OfferOrder></OfferOrder>
<OfferOrder></OfferOrder>
</OrderData>
</Order>
Now when I'm Deserializng string orderxml containing the XML, It doesn't fill <OfferOrder> into my OrderData object.
XmlSerializer xmlserializer = new XmlSerializer((typeof(Order)));
using (TextReader reader = new StringReader(orderxml))
{
order = (Order)xmlserializer.Deserialize(reader);
}
Classes:
public partial class Order
{
private OrderOrderData orderDataField;
public OrderOrderData OrderData
{
get
{
return this.orderDataField;
}
set
{
this.orderDataField = value;
}
}
}
public partial class OrderOrderData
{
private OrderOrderDataOfferOrder[] offerOrderField;
public OrderOrderDataOfferOrder[] OfferOrder
{
get
{
return this.offerOrderField;
}
set
{
this.offerOrderField = value;
}
}
}
Is something wrong with my classes?
There are some issues in your code. First you can make your properties to auto-implemented properties, that is omit the private backing-fields and write this instead:
public MyType MyProperty { get; set; }
Second you have to provide the names of the tags within the Xml to the serializer. In your case the names within the xml and those in your class-structure are equal, so you can omit the names also. However just for completeness:
public class Order
{
[XmlElement("OrderData")
public OrderOrderData OrderData { get; set; }
}
public class OrderOrderData
{
[XmlElement("OfferOrder")
public OrderOrderDataOfferOrder[] OfferOrder { get; set; }
}
The reason why it doesn´t work for you is that arrays are usually serialized with a nested element whose name in your case would be OfferOrders. Within that tag you have then the arrays elements. Usually it´s not wanted to have that further nesting, what you want instead is to flatten the elements of the array directly into your actual data-class. To do so add an XmlElement upfront your array-declaration with the name of the elements, in my code OfferOrder.
When I'm deserializing a xml string, I need to save a XElement outerXml on a string property called prop2.
My XML:
<MyObj>
<prop1>something</prop1>
<prop2>
<RSAKeyValue>
<Modulus>...</Modulus>
<Exponent>...</Exponent>
</RSAKeyValue>
</prop2>
<prop3></prop3>
</MyObj>
My object:
public class MyObj
{
[XmlElement("prop1")]
public string prop1 { get; set; }
[XmlText]
public string prop2 { get; set; }
[XmlElement(ElementName = "prop3", IsNullable = true)]
public string prop3 { get; set; }
}
I'm deserializing using XmlSerializer, like this:
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(new StringReader(myXmlString));
I tried to use [XmlText] to save XML text in prop2 But I'm only getting null.
What I need to do to save <RSAKeyValue><Modulus>...</Modulus><Exponent>...</Exponent></RSAKeyValue> like text in prop2?
XmlText will give value as XML encoded as text (">prop2<...") see XmlTextAttribute
By default, the XmlSerializer serializes a class member as an XML element. However, if you apply the XmlTextAttribute to a member, the XmlSerializer translates its value into XML text. This means that the value is encoded into the content of an XML element.
One possible solution - use XmlNode as type of the property:
public class MyObj
{
[XmlElement("prop1")]
public string prop1 { get; set; }
public XmlNode prop2 { get; set; }
[XmlElement(ElementName = "prop3", IsNullable = true)]
public string prop3 { get; set; }
}
var r = (MyObj)serializer.Deserialize(new StringReader(myXmlString));
Console.WriteLine(r.prop2.OuterXml);
Alternatively you may make whole object implement custom Xml serialization or have custom type that matches XML (to read normally) and have additional property to represent that object as XML string.
I am writing a WCF service that generates various XML and JSON formats for multiple clients. The code below generates a SerializationException: 'TPH_PriceListJsonItems' is a collection type and cannot be serialized when assigned to an interface type that does not implement IEnumerable ('TPH_IPriceListItems'). The XML part is working fine, but not JSON. I do not understand the error, my interface is implementing IEnumerable to represent a class wrapping a simple List<> so I can use the CollectionDataContract.
public class ReproduceDataContractIssue
{
public static void Main(String[] args)
{
// Create test object - vacation products lowest prices grid
TPH_IPriceList priceList = new TPH_PriceListJson();
priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Cancun", StayDuration = 7, LowestPrice = 1111 });
priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Jamaica", StayDuration = 14, LowestPrice = 2222 });
// Serialize into XML string
DataContractSerializer serializer = new DataContractSerializer(priceList.GetType());
MemoryStream memStream = new MemoryStream();
serializer.WriteObject(memStream, priceList);
memStream.Seek(0, SeekOrigin.Begin);
string xmlOutput;
using (var streamReader = new StreamReader(memStream))
xmlOutput = streamReader.ReadToEnd();
// Serialize into JSON string
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(priceList.GetType());
jsonSerializer.WriteObject(memStream = new MemoryStream(), priceList);
memStream.Seek(0, SeekOrigin.Begin);
string jsonOutput;
using (var streamReader = new StreamReader(memStream))
jsonOutput = streamReader.ReadToEnd();
}
}
public interface TPH_IPriceList
{
TPH_IPriceListItems ListItems { get; set; }
}
public interface TPH_IPriceListItems : IEnumerable<TPH_IPriceListItem>, IEnumerable, IList<TPH_IPriceListItem>
{
}
public interface TPH_IPriceListItem
{
string DestCityName { get; set; }
int StayDuration { get; set; }
int LowestPrice { get; set; }
}
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[DataMember]
public TPH_IPriceListItems ListItems { get; set; }
public TPH_PriceListJson()
{
ListItems = new TPH_PriceListJsonItems();
}
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
[DataMember(Order = 1)]
public string DestCityName { get; set; }
[DataMember(Order = 2)]
public int StayDuration { get; set; }
[DataMember(Order = 3)]
public int LowestPrice { get; set; }
public TPH_PriceListJsonItem()
{
}
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>, TPH_IPriceListItems, IEnumerable<TPH_IPriceListItem>, IEnumerable
{
public TPH_PriceListJsonItems(int capacity)
: base(capacity)
{
}
public TPH_PriceListJsonItems()
: base()
{
}
}
}
The inconsistency arises from a difference in how JSON and XML represent collections. For XML, the data contract serializers convert a collection to a nested set of elements -- an outer collection wrapper, and an inner element for each item in the collection. For JSON, the serializers convert a collection to an array containing objects. This seems reasonable, but there's a difference between the two: the XML outer element can have its own XML attributes, but JSON arrays cannot have their own properties -- there's simply no place for them in the standard.
This becomes an issue in dealing with type hints. Type hints are properties added to the serialized data to indicate, in the event of serializing an interface or base class of a class hierarchy, what concrete class was actually serialized. They are required to enable deserialization of the object without data loss. In XML, they appear as an i:type attribute:
<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V1">
<ListItems i:type="ListItems"> <!-- Notice the type hint here. -->
<ListItem i:type="TPH_PriceListJsonItem"> <!-- Notice the type hint here also. -->
<DestCityName>Cancun</DestCityName>
<StayDuration>7</StayDuration>
<LowestPrice>1111</LowestPrice>
</ListItem>
</ListItems>
</PriceList>
As you can see from your own example, type hints can be added for both collection and non-collection classes.
In JSON objects, they appear as an added property named "__type":
{
"__type": "TPH_PriceListJsonItem:#Question32569055.V3",
"DestCityName": "Cancun",
"StayDuration": 7,
"LowestPrice": 1111
}
But, as mentioned before, JSON arrays cannot have properties. So, what does DataContractJsonSerializer do for polymorphic collection types? Well, other than for a few standard collection interfaces which, as Fabian notes, are mapped to collection classes using hardcoded logic, it throws a cryptic exception to indicate that subsequent deserialization would be impossible. (For comparison, Json.NET introduces an extra container object to hold collection type information. See TypeNameHandling setting.)
The solution to this inconsistency is to serialize the collection explicitly as a concrete collection (TPH_PriceListJsonItems in your case) rather than as an interface:
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[IgnoreDataMember]
public TPH_IPriceListItems ListItems
{
get
{
return ListItemList;
}
set
{
var list = value as TPH_PriceListJsonItems;
if (list == null)
{
list = new TPH_PriceListJsonItems();
if (value != null)
list.AddRange(value);
}
ListItemList = list;
}
}
[DataMember(Name = "ListItems")]
TPH_PriceListJsonItems ListItemList { get; set; }
public TPH_PriceListJson()
{
ListItemList = new TPH_PriceListJsonItems();
}
}
This eliminates the need for the type hint on the collection element while retaining it for collection members. It generates the following XML:
<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V3">
<ListItems> <!-- No type hint here any more. -->
<ListItem i:type="TPH_PriceListJsonItem"> <!-- But the type hint is still here. -->
<DestCityName>Cancun</DestCityName>
<StayDuration>7</StayDuration>
<LowestPrice>1111</LowestPrice>
</ListItem>
</ListItems>
</PriceList>
And produces the following JSON:
{
"ListItems": [
{
"__type": "TPH_PriceListJsonItem:#Question32569055.V3",
"DestCityName": "Cancun",
"StayDuration": 7,
"LowestPrice": 1111
},
]
}
This design allows the class TPH_IPriceListItems to control exactly what type of collection is used internally rather than leaving it up to users of the class, and thus seems like a preferable design overall.
Looks like it is a similar problem to this one. The List of supported interfaces in DataContractJsonSerializer is hardcoded. So you can't add your own List Wrapper interface.
Why don't you just drop TPH_IPriceListItems like in the following code ? It is simpler and should also do what you want:
public interface TPH_IPriceList
{
IList<TPH_IPriceListItem> ListItems { get; set; }
}
public interface TPH_IPriceListItem
{
string DestCityName { get; set; }
int StayDuration { get; set; }
int LowestPrice { get; set; }
}
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[DataMember]
public IList<TPH_IPriceListItem> ListItems { get; set; }
public TPH_PriceListJson()
{
ListItems = new TPH_PriceListJsonItems();
}
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
[DataMember(Order = 1)]
public string DestCityName { get; set; }
[DataMember(Order = 2)]
public int StayDuration { get; set; }
[DataMember(Order = 3)]
public int LowestPrice { get; set; }
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>
{
}
I need to deserialize the following Json, which according to Jsonlint.com is valid, but ive not come across this before or cannot find examples of similar Json and how to deal with it?
[1,"Bellegrove / Sherwood ","76705","486","Bexleyheath Ctr",1354565507000]
My current system with like this:
Data class:
[DataContract]
public class TFLCollection
{ [DataMember(Name = "arrivals")]
public IEnumerable<TFLB> TFLB { get; set; }
}
[DataContract]
public class TFLB
{
[DataMember]
public string routeName { get; set; }
[DataMember]
public string destination { get; set; }
[DataMember]
public string estimatedWait { get; set; }
}
Deserializer:
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(TFLCollection));
using (var stream = new MemoryStream(Encoding.Unicode.GetBytes(result)))
{ var buses = (TFLCollection)serializer.ReadObject(stream);
foreach (var bus in buses.TFLBuses)
{
StopFeed _item = new StopFeed();
_item.Route = bus.routeName;
_item.Direction = bus.destination;
_item.Time = bus.estimatedWait;
listBox1.Items.Add(_item);
My exsiting deserializer works with a full Json stream and iterates through it, but in my new Json I need to deserialize, it only have 1 item, so I wont need to iterate through it.
So is it possible to deserialize my Json example using a similar method than I currently do?
I would say that you are attempting to overcomplicate things. What you have is a perfectly formed json array of strings. If I were you I would deserialize that to an .net array first, and then write a 'mapper' function to copy the values across:
public TFLB BusRouteMapper(string[] input)
{
return new TFLB {
Route = input[x],
Direction = input[y],
};
}
And so on. Of course this assumes that you know what order your json is going to be in, but if you are attempting this in the first place then you must do!
I am deserializing the JSON string to root object by using the following class which works fine .
[Serializable]
public class MoviesListRootObject
{
public int count { get; set; }
public Pagination pagination { get; set; }
public List<Response> response { get; set; }
}
...................................
var json = wc.DownloadString(jsonRequestURL);
var rootObj = JsonConvert.DeserializeObject<MoviesListRootObject>(json);
But if I am generalizng the root object bt creating parent class and then inheriting from it , then I get null after deserialization!!!!
[Serializable]
public class RootObject
{
public int count { get; set; }
public Pagination pagination { get; set; }
}
[Serializable]
public class MoviesListRootObject:RootObject
{
public List<MovieResponse> movieResponse { get; set; }
}
..............................................
var json = wc.DownloadString(jsonRequestURL);
var rootObj = JsonConvert.DeserializeObject<MoviesListRootObject>(json);
It is quite simple and out of the box support provided by json.net, you just have to use the following JsonSettings while serializing and Deserializing:
JsonConvert.SerializeObject(graph, Formatting.None, new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});
and for Deserializing use the below code:
JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bData), type,
new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
});
Just take a note of the JsonSerializerSettings object initializer, that is important for you.
Assuming json string looks like the following
{"movieResponse":[{"Rating":"Good"}],"count":1,"pagination":{"PageIndex":1}}
I find it works fine with me. I am currently using Json.net 4.5 r11
If you are serialised object when the class structure looks like
[Serializable]
public class MoviesListRootObject
{
public int count { get; set; }
public Pagination pagination { get; set; }
public List<Response> response { get; set; }
}
And the json string looks something like below
{"count":1,"pagination":{"PageIndex":1},"response":[{"Rating":"Good"}]}
And now you are using the new structure to deserialise then you will get null movieResponse since the property is changed in the new structure.
To solve this problem create a new custom jsonConverter deriving from JsonConverter and create your object programatically. Please have a look at the link json-deserialization-with-jsonnet-class to get some idea. In case if you already know about this and the problem still exists then please update the question with more detail like Json.net version used, json string, complete class structure etc
HTH.