I get a System.FormatException thrown when i try to parse XML into an object. As far as I can tell, it's due to the culture used in System.Xml.Serialization.XmlSerializer.Deserialize, wich expects a dot as the decimal character, but the xml contains a comma.
The object looks as follows:
public sealed class Transaction
{
[XmlElement("transactionDate")]
public DateTime TransactionDate { get; set; }
[XmlElement("transactionAmount")]
public decimal Amount { get; set; }
[XmlElement("transactionDescription")]
public string Description { get; set; }
[XmlElement("transactionType")]
public int Type { get; set; }
public static Transaction FromXmlString(string xmlString)
{
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(Transaction));
var instance = (Transaction) serializer.Deserialize(reader);
return instance;
}
}
The xml:
<transaction>
<transactionDate> 2013-07-02 <transactionDate>
<transactionAmount>-459,00</transactionAmount>
<transactionDescription>description</transactionDescription>
<transactionType>1</transactionType>
</transaction>
I've made it work by introducing a second property that parses the first using my own culture:
namespace MyNamespace
{
[XmlRoot("transaction"), XmlType("Transaction")]
public sealed class Transaction
{
[XmlElement("transactionDate")]
public DateTime TransactionDate { get; set; }
[XmlElement("transactionAmount")]
public string Amount { get; set; }
public decimal AmountAsDecimal {
get
{
decimal value;
Decimal.TryParse(Amount, NumberStyles.Any, CultureInfo.CreateSpecificCulture("sv-SE"), out value);
return value;
}
}
[XmlElement("transactionDescription")]
public string Description { get; set; }
[XmlElement("transactionType")]
public int Type { get; set; }
public static Transaction FromXmlString(string xmlString)
{
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(Transaction));
var instance = (Transaction) serializer.Deserialize(reader);
return instance;
}
}
}
which exposes an extra property that i don't want there.
So my question is: is there another way to do this, without iterating over each element and parsing/assigning it to the object "manually"?
XML serializer uses a standardized Number and DateTime format, the standard is defined in the W3C schema datatype specification http://www.w3.org/TR/xmlschema-2/.
Don't expect XmlSerializer to pay attention to the thread's CultureInfo, it intentionally uses a standardized format to ensure you can serialize/deserialize independent of the culture/locale.
What you can do instead is have a property that will be used to serialize/deserialize the decimal.
See: Partially deserialize XML to Object
[XmlType("transaction")]
public sealed class Transaction
{
[XmlElement("transactionDate")]
public DateTime TransactionDate { get; set; }
[XmlIgnore]
public decimal Amount { get; set; }
[XmlElement("transactionAmount")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public string AmountSerialized
{
get
{
return Amount.ToString(CultureInfo.CreateSpecificCulture("sv-SE"));
}
set
{
decimal amount;
Decimal.TryParse(value, NumberStyles.Any, CultureInfo.CreateSpecificCulture("sv-SE"), out amount);
Amount = amount;
}
}
[XmlElement("transactionDescription")]
public string Description { get; set; }
[XmlElement("transactionType")]
public int Type { get; set; }
public static Transaction FromXmlString(string xmlString)
{
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(Transaction));
var instance = (Transaction) serializer.Deserialize(reader);
return instance;
}
}
This way you can get/set the Amount without needing to worry about how it is serialized. Since this is a DTO you can create another class without the AmountSerialized as your domain object (and use something like AutoMapper to make conversion painless).
Usage:
var data = #"<transaction>
<transactionDate>2013-07-02</transactionDate>
<transactionAmount>-459,00</transactionAmount>
<transactionDescription>description</transactionDescription>
<transactionType>1</transactionType>
</transaction>";
var serializer = new XmlSerializer(typeof(Transaction));
using(var stream = new StringReader(data))
using(var reader = XmlReader.Create(stream))
{
Console.Write(serializer.Deserialize(reader));
}
Also there was a typo in the ending tag for transactionDate.
If you know the culture that the XML was generated in, one easy solution is to switch the current thread's culture to that culture prior to deserializing.
System.Globalization.CultureInfo oCurrentCulture = null;
try
{
// Save the current culture
oCurrentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");
// Do your work
}
finally
{
// Restore the saved culture
System.Threading.Thread.CurrentThread.CurrentCulture = oCurrentCulture;
}
Related
I have a requirement where I need a property that is actually a JSON value from a MongoDB collection that needs to be deserialized into a string. This conversion is throwing a "Cannot deserialize a 'String' from a BsonType 'Document'" exception.
I tried implementing a JSON custom converter, but as the value is being treated as a BsonDocument, it is not helping and I am getting the same exception. I also need it in the original format as I need to cast it back into a BsonDocument down the line. I guess I would need a custom Bson serializer/deserializer.
Incoming sample document from MongoDB collection:
{
"name": "Jane Doe",
"dob": {
"month": "Sep",
"day": 09,
"year": 1987
}
}
Type it is expecting for deserialization:
public class Person
{
public string name { get; set; }
public Dob dob { get; set; }
public class Dob
{
public string month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
}
Type I want it to deserialize into:
public class Person
{
public string name { get; set; }
public string dob { get; set; }
}
To summarize, you have a public-facing string property on your model that contains JSON which you would like to internally serialize to MongoDB by deserializing the JSON string to some intermediate DTO, then serializing the DTO itself to Mongo.
Here are a couple of approaches to solving your problem.
Firstly, you could introduce a private DTO-valued property Dob SerializedDOB { get; set; } into your data model, mark that property with [BsonElement("dob")] to force it to be serialized, then modify dob to be a non-serialized surrogate property that serializes from and to the underlying SerializedDOB within its getter and setter. The following code shows this approach:
public class Person
{
public string name { get; set; }
[BsonIgnore]
public string dob
{
get => BsonExtensionMethods.ToJson(SerializedDOB);
set => SerializedDOB = MyBsonExtensionMethods.FromJson<Dob>(value);
}
[BsonElement("dob")]
Dob SerializedDOB { get; set; }
class Dob // The DTO
{
public string month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
}
The advantage of this approach is that, by making the JSON string a surrogate, the setter automatically ensures that it is well-formed.
Demo fiddle #1 here.
Secondly, you could create a custom SerializerBase<string> for dob that maps the string value to and from the DTO Dob during (de)serialization. The following code shows this approach:
public class Person
{
public string name { get; set; }
[BsonSerializer(typeof(JsonStringAsObjectSerializer<Dob>))]
public string dob { get; set; }
class Dob // The DTO
{
public string month { get; set; }
public int day { get; set; }
public int year { get; set; }
}
}
public class JsonStringAsObjectSerializer<TObject> : SerializerBase<string> where TObject : class
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
{
if (value == null)
{
var bsonWriter = context.Writer;
bsonWriter.WriteNull();
}
else
{
var obj = MyBsonExtensionMethods.FromJson<TObject>(value);
var serializer = BsonSerializer.LookupSerializer(typeof(TObject));
serializer.Serialize(context, obj);
}
}
public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
var serializer = BsonSerializer.LookupSerializer(typeof(TObject));
var obj = (TObject)serializer.Deserialize(context);
return (obj == null ? null : BsonExtensionMethods.ToJson(obj));
}
}
The advantage of this approach is that JsonStringAsObjectSerializer<TObject> can be reused whenever this requirement arises.
Demo fiddle #2 here.
The following extension method is used with both solutions to deserialize the JSON string to a specified type because, confusingly, BsonExtensionMethods has a ToJson() method but no FromJson() method:
public static partial class MyBsonExtensionMethods
{
// Not sure why but BsonExtensionMethods.cs seems to lack methods for deserializing from JSON, so I added some here.
// See https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Bson/BsonExtensionMethods.cs
public static TNominalType FromJson<TNominalType>(
string json,
JsonReaderSettings readerSettings = null,
IBsonSerializer<TNominalType> serializer = null,
Action<BsonDeserializationContext.Builder> configurator = null)
{
return (TNominalType)FromJson(json, typeof(TNominalType), readerSettings, serializer, configurator);
}
public static object FromJson(
string json,
Type nominalType,
JsonReaderSettings readerSettings = null,
IBsonSerializer serializer = null,
Action<BsonDeserializationContext.Builder> configurator = null)
{
if (nominalType == null || json == null)
throw new ArgumentNullException();
serializer = serializer ?? BsonSerializer.LookupSerializer(nominalType);
if (serializer.ValueType != nominalType)
throw new ArgumentException(string.Format("serializer.ValueType {0} != nominalType {1}.", serializer.GetType().FullName, nominalType.FullName), "serializer");
using (var textReader = new StringReader(json))
using (var reader = new JsonReader(textReader, readerSettings ?? JsonReaderSettings.Defaults))
{
var context = BsonDeserializationContext.CreateRoot(reader, configurator);
return serializer.Deserialize(context);
}
}
}
Using C#, is there a way to easily parse an XML file so that it can be used as an object?
Example XML:
<Config>
<Ui>
<Colour>black</Colour>
<Size>small</Size>
</Ui>
<Output>
<Mode>smb</Mode>
<Version>2</Version>
</Output>
</Config>
And then refer to the parameters in my application by
Config.Output.Mode
I've tried this method - How to Deserialize XML document
But when I try
var cfg = new Config();
cfg.Load(#"config.xml");
Console.WriteLine(cfg.Output.Mode);
visual studio indicates .Output.Mode is not valid.
Where Config.Load is
xmlData = File.ReadAllText(configPath);
var serializer = new XmlSerializer(typeof(Config));
using (var reader = new StringReader(xmlData))
{
Config result = (Config)serializer.Deserialize(reader);
}
You have to create the classes that match the definition in the xml file in order to deserialize the file into an instance of the class. Note that I've named the properties with the same name as we have in the xml file. If you want to use different property names, then you'd need to add an attribute above the property that specifies the xml element that should map to it (like for the Ui, you would add the attribute: [XmlElement("Ui")]).
Note that I've also overridden the ToString methods for the classes so we can output them to the console in a nice fashion:
public class Config
{
public UI Ui { get; set; }
public Output Output { get; set; }
public override string ToString()
{
return $"Config has properties:\n - Ui: {Ui}\n - Output: {Output}";
}
}
public class UI
{
public string Colour { get; set; }
public string Size { get; set; }
public override string ToString()
{
return $"(Colour: {Colour}, Size: {Size})";
}
}
public class Output
{
public string Mode { get; set; }
public int Version { get; set; }
public override string ToString()
{
return $"(Mode: {Mode}, Version: {Version})";
}
}
Now all we have to do is create a StreamReader, point it to our file path, and then use the XmlSerializer class to Deserialize the file (casting the output to the appropriate type) into an object:
static void Main(string[] args)
{
var filePath = #"f:\private\temp\temp2.txt";
// Declare this outside the 'using' block so we can access it later
Config config;
using (var reader = new StreamReader(filePath))
{
config = (Config) new XmlSerializer(typeof(Config)).Deserialize(reader);
}
Console.WriteLine(config);
GetKeyFromUser("\n\nDone! Press any key to exit...");
}
Output
Here are the classes:
public class Config
{
public UI UI { get; set; }
public Output Output { get; set; }
}
public struct UI
{
public string Colour { get; set; }
public string Size { get; set; }
}
public struct Output
{
public string Mode { get; set; }
public int Version { get; set; }
}
The Deserialize function:
public static T Deserialize<T>(string xmlString)
{
if (xmlString == null) return default;
var serializer = new XmlSerializer(typeof(T));
using (var reader = new StringReader(xmlString))
{
return (T) serializer.Deserialize(reader);
}
}
And here's a working version:
Config cfg = Deserialize<Config>(xmlString);
Console.WriteLine(cfg.Output.Mode);
I'm trying to set up a very small database using XML serialization and more specifically XmlSerializer.
My main class is the following :
public class XmlDB
{
[XmlIgnore]
public string FilePath { get; private set; }
public List<FooType> Foos { get; set; }
public List<BarType> Bars { get; set; }
public List<ThirdType> Thirds { get; set; }
private XmlDB():this(null) { }
public XmlDB(string strDBPath) {
this.FilePath = strDBPath;
this.Foos = new List<FooType>();
this.Bars = new List<BarType>();
this.Thirds = new List<ThirdType>();
}
public static XmlDB Load(string strDBPath) {
using (XmlReader reader = XmlReader.Create(strDBPath)) {
XmlDB db = (XmlDB)new XmlSerializer(typeof(XmlDB)).Deserialize(reader);
db.FilePath = strDBPath;
return db;
}
}
public void SaveChanges() {
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create(this.FilePath, settings)) {
XmlSerializer ser = new XmlSerializer(typeof(XmlDB));
ser.Serialize(writer, this);
}
}
}
My test method creates an instance, populates the lists and calls the SaveChanges method.
Everything works fine on serialization and the Xml output looks consistent.
The problem happens on deserializing : No error is reported but only the first item of the first List is treated, the following items of the first list are not deserialized, neither are the following lists...
If I shuffle the order of the lists in the Xml, it's always the first item of the first list in the Xml file that is deserialized.
I tried the following simple test to confirm (which unfortunately works fine, all lists are populated on deserializing) :
public class DBTestList
{
public List<DBTest> TestList { get; set; }
public List<DBTest2> TestList2 { get; set; }
public DBTestList() {
this.TestList = new List<DBTest>();
this.TestList2 = new List<DBTest2>();
}
}
public class DBTest
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public class DBTest2
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public void TestSerialProblem() {
//Init data
DBTestList tl = new DBTestList();
tl.TestList.Add(new DBTest() { TestInt = 1, TestStr = "test11" });
tl.TestList.Add(new DBTest() { TestInt = 2, TestStr = "test12" });
tl.TestList2.Add(new DBTest2() { TestInt = 3, TestStr = "test21" });
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create("test.db", settings)) {
XmlSerializer ser = new XmlSerializer(typeof(DBTestList));
ser.Serialize(writer, tl);
}
using (XmlReader reader = XmlReader.Create("test.db")) {
DBTestList db = (DBTestList)new XmlSerializer(typeof(DBTestList)).Deserialize(reader);
Assert.IsTrue(db.TestList2[0].TestStr == "test21");
}
}
I read a lot of posts on this subject but none helped.
Do you have an idea ?
Thanks,
Best regards.
EDIT :
To give a more detailed idea of the classes used in the lists, here's one basic implementation.
All the types are derived from the parent one a_SolidElement, adding only a few properties (basic value types and/or enum) :
public abstract class a_SolidElement
{
[XmlIgnore]
public int Position { get; set; }
public virtual double Thickness { get; set; }
public virtual double Density { get; set; }
public string SupplierName { get; set; }
public string Name { get; set; }
}
public enum ElementType
{
Undefined=0,
TypeA,
TypeB
}
public class FooType:a_SolidElement
{
public double AdditionalData { get; set; }
public e_ElementType ElementType { get; set; }
}
dbc was actually right about the bad IXmlSerializable implementation in his comment :
In one of my classes, I had one property of a type I didn't write, with a problem in the readXml method.
I didn't see it at first because I successively removed some properties to see which one caused the problem but this one was still in the first deserialized item so that even if the subsequent ones didn't have it, the reader was still already messed up by the first one.
It's so obvious now that I feel bad for asking the question in the first place !
Thank you very much for the help !
I'm trying to deserialize json data with DataContractJsonSerializer class. a problem is how to set root element?
my json data is here.
{
"delete":{
"status":{
"id":696142765093072896,
"id_str":"696142765093072896",
"user_id":2223183576,
"user_id_str":"2223183576"
},
"timestamp_ms":"1454808363540"
}
}
and I wrote class for deserialization like this. but it isn't works. my Status always null.
[DataContract(Name="delete")]
public class Delete
{
[DataMember(Name="status")]
public DeletedStatus Status { get; set; }
}
public class DeletedStatus
{
[DataMember(Name = "id")]
public long Id { get; set; }
[DataMember(Name = "user_id")]
public long UserId { get; set; }
}
how can I start parse json from specific element?
Based on what I can tell from the JSON, the deserialization appears to be failing because the root property of the object is the "delete" property. I don't believe this will work with the DataContractJsonSerializer simply because the given type will not match the Delete type. One other possible issue is that I see the DeleteStatus class is missing a [DataContract] attribute.
Long story short, there is no simple way of doing what you want to do. That being said, there is a short and sweet way of deserializing the JSON without adding a lot of extra headache. I suggest creating a data type that represents the JSON in its current state, and deserialize to that type instead.
I wrote a Unit Test that you can run from a Visual Studio test project. I hope this helps.
JsonDeserializationTests.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Runtime.Serialization.Json;
using System.IO;
namespace SerializationTests {
[TestClass]
public class JsonDeserializationTests {
[TestMethod]
public void Deserialize_Delete_Type_Success() {
string json = string.Empty;
//Set the DataContractJsonSerializer target type to our wrapper type.
var ser = new DataContractJsonSerializer(typeof(DeleteWrapperJsonResult));
//Create an instance of the wrapper that reflects the JSON that you gave.
//This will help me mock the data that you gave.
var deleteWrapper = new DeleteWrapperJsonResult {
delete = new DeleteJsonResult {
status = new DeletedStatusJsonResult {
id = 696142765093072896,
user_id = 2223183576
}
}
};
//Convert the mock data to JSON to reflect the JSON that you gave.
using (var serStream = new MemoryStream()) {
using (var sr = new StreamReader(serStream)) {
ser.WriteObject(serStream, deleteWrapper);
serStream.Position = 0;
json = sr.ReadToEnd(); //Set the JSON string here.
//Output "{\"delete\":{\"status\":{\"id\":696142765093072896,\"id_str\":\"696142765093072896\",\"user_id\":2223183576,\"user_id_str\":\"2223183576\"}}}"
}
}
//Prepeare to Deserialize the JSON.
var deserialized = default(DeleteWrapperJsonResult);
using (var deserStream = new MemoryStream()) {
using (var sw = new StreamWriter(deserStream)) {
sw.Write(json); //Write the JSON to the MemoryStream
sw.Flush();
deserStream.Seek(0, SeekOrigin.Begin);
//Deserialize the JSON into an instance of our wrapper class.
//This works because of the structure of the JSON.
deserialized = (DeleteWrapperJsonResult)ser.ReadObject(deserStream);
}
}
//Initialize the actual Delete instanace with what was deserialized.
var delete = new Delete {
Status = new DeletedStatus {
//These values were populated with the JSON values.
UserId = deserialized.delete.status.user_id,
Id = deserialized.delete.status.id
}
};
//Write asserts around what was given and check for equality.
Assert.AreEqual(delete.Status.UserId, deleteWrapper.delete.status.user_id);
Assert.AreEqual(delete.Status.Id, deleteWrapper.delete.status.id);
//Test Passes for Me
}
}
}
Delete.cs
using System.Runtime.Serialization;
namespace SerializationTests {
[DataContract]
[KnownType(typeof(Delete))]
public class Delete {
[DataMember]
public DeletedStatus Status { get; set; }
}
[DataContract]
[KnownType(typeof(DeletedStatus))]
public class DeletedStatus {
[DataMember]
public long Id { get; set; }
[DataMember]
public long UserId { get; set; }
}
/**************************************************************
These types below are what comprise our wrapper class so that we can
use the JSON in its current state. The wrapper classes have properties that
are synonymous with the JSON properties.
**************************************************************/
//This structure represents the object nesting as it appears currently in your example.
[DataContract]
[KnownType(typeof(DeleteJsonResult))]
public class DeleteWrapperJsonResult {
[DataMember]
public DeleteJsonResult delete { get; set; }
}
[DataContract]
[KnownType(typeof(DeleteJsonResult))]
public class DeleteJsonResult {
[DataMember]
public DeletedStatusJsonResult status { get; set; }
}
[DataContract]
[KnownType(typeof(DeletedStatusJsonResult))]
public class DeletedStatusJsonResult {
[DataMember]
public long id { get; set; }
[DataMember]
public string id_str {
get {
return id.ToString();
}
set {
return;
}
}
[DataMember]
public long user_id { get; set; }
[DataMember]
public string user_id_str {
get {
return user_id.ToString();
}
set {
return;
}
}
}
}
As of the time of this writing, my unit test is passing! Let me know if I can assist further.
I am trying to post some data to my MVC 3 controller through a hidden text field that contains some JSON. I have that JSON passed in via string coursesList. Anyone have an idea why this is not working?
All I'm doing is making a byte [] out of the JSON string, writing it to a MemoryStream, and deserializing that stream -- or, attempting to. BookCourse bc always ends up with null properties.
Here's something like the JSON I would be using:
[{"coursesection":"1234","netlogon":"jsmith","label":"CRSE-1313 Generic Course Titling ~ Joe Smith"}]
And here's the object to be deserialized into:
using System.Runtime.Serialization;
namespace xxxx.Models
{
[DataContract]
public class BookCourse
{
[DataMember]
public string coursesection { get; set; }
[DataMember]
public string netlogon { get; set; }
[DataMember]
public string label { get; set; }
}
}
Finally, the controller action code to do it --
var byteArray = Encoding.ASCII.GetBytes(coursesList);
// Deserialize byte array to data type
var stream = new MemoryStream();
stream.Write(byteArray, 0, byteArray.Length);
var crs = new DataContractJsonSerializer(typeof(BookCourse));
stream.Position = 0;
// Read stream to object
ad.CourseSectionIDs = new List<int>();
try
{
var bc = (BookCourse) crs.ReadObject(stream);
while (bc.coursesection != null)
{
cs.AssociateCourseBook(bc.netlogon, bc.coursesection, ad.ISBN);
bc = (BookCourse)crs.ReadObject(stream);
}
}
catch (System.Runtime.Serialization.SerializationException e)
{
// Is this best practice for handling "none"?
}
Your JSON string represents a collection of BookCourse, not a single BookCourse. So adapt your code:
var serializer = new DataContractJsonSerializer(typeof(BookCourse[]));
and then:
var bookCourses = (BookCourse[])crs.ReadObject(stream);
or if you want to work with a single BookCourse you will need to change your JSON string and remove the wrapping square brackets which represent a collection.
Darin is correct, here is the change if you want to do it on a contract level.
[DataContract]
public class BookCourse
{
[DataMember]
public string coursesection { get; set; }
[DataMember]
public string netlogon { get; set; }
[DataMember]
public string label { get; set; }
}
[DataContract]
public class BookCourceCollection
{
[DataMember]
public List<BookCourse> Collection;
public static BookCourceCollection ReturnCollection(string jsonString)
{
MemoryStream ms;
BookCourceCollection collection;
using (ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCourceCollection));
collection = ser.ReadObject(ms) as BookCourceCollection;
}
return collection;
}
}
Usage:
string jsonString = "Your JSON string from the front end";
var bookCourceObject = BookCourseCollection.ReturnCollection(jsonString);
foreach (BookCourse bookCourse in bookCourceObject.Collection)
{
cs.AssociateCourseBook(bookCourse.netlogon, bookCourse.coursesection, bookCourse.ISBN);
}