I am trying to serialise/deserialise a .NET DataSet using Json.NET and a custom serialiser. I know many of you will tell me not to (I have seen this on other posts) I have a valid reason and wish to continue down this route.
My serialisation is based upon the fact that the .NET DataSet can export it's schema and data to XML and then re-import the same; on that basis, I am trying to create a converter that will allow me to capture that XML, convert it to JSON then convert it back and reload it. My implementation is as follows...
class DataSetConverter : JsonConverter<DataSet>
{
public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
{
DataSet dataSet = new DataSet();
JObject jObject = JObject.Load(reader);
String json = jObject.ToString();
XDocument document = JsonConvert.DeserializeXNode(json);
using (MemoryStream memoryStream = new MemoryStream())
using (StreamWriter streamWriter = new StreamWriter(memoryStream))
{
streamWriter.Write(document.ToString(SaveOptions.None));
streamWriter.Flush();
memoryStream.Position = 0;
dataSet.ReadXml(memoryStream);
}
return dataSet;
}
public override void WriteJson(JsonWriter writer, DataSet dataSet, JsonSerializer serializer)
{
using (MemoryStream memoryStream = new MemoryStream())
{
dataSet.WriteXml(memoryStream, XmlWriteMode.WriteSchema);
using (StreamReader reader = new StreamReader(memoryStream))
{
memoryStream.Seek(0, SeekOrigin.Begin);
XDocument document = XDocument.Parse(reader.ReadToEnd());
writer.WriteRaw(JsonConvert.SerializeXNode(document, Formatting.Indented, false));
}
}
}
}
Used as follows (purely serialising a DataSet object) it works (my new DataSet has the same schema and data as the original)...
DataSet originalInserts = new DataSet("Inserts");
DataTable originalStuff = originalInserts.Tables.Add("Stuff");
originalStuff.Columns.Add("C1", typeof(String));
originalStuff.Columns.Add("C2", typeof(Int64));
originalStuff.Columns.Add("C3", typeof(Guid));
originalStuff.Columns.Add("C4", typeof(float));
originalStuff.Rows.Add("One", 2, Guid.NewGuid(), 4.4);
String json = JsonConvert.SerializeObject(originalInserts, Formatting.Indented, new DataSetConverter());
DataSet newInsertsFromConvertedXml = (DataSet)JsonConvert.DeserializeObject(json, typeof(DataSet), new DataSetConverter());
However if I then try and use the same converter with an object that contains a DataSet
(the very same DataSet
as above)...
public class TestClass
{
public DataSet Inserts { get; set; }
public String SomethingElse { get; set; }
}
TestClass testClass = new TestClass { Inserts = originalInserts, SomethingElse = "Me" };
json = JsonConvert.SerializeObject(testClass, Formatting.Indented, new DataSetConverter());
It fails with
Token PropertyName in state Property would result in an invalid JSON object. Path ''.
I have also tried decorating the DataSet
on the TestClass
with a JsonConverter
attribute and removing the converter from the Serialize method call but get the same result...
public class TestClass
{
[JsonConverter(typeof(DataSetConverter))]
public DataSet Inserts { get; set; }
public String SomethingElse { get; set; }
}
TestClass testClass = new TestClass { Inserts = originalInserts, SomethingElse = "Me" };
json = JsonConvert.SerializeObject(testClass, Formatting.Indented);
What am I missing?
Your basic problem is that you should be calling WriteRawValue()
instead of WriteRaw()
:
writer.WriteRawValue(JsonConvert.SerializeXNode(document, Formatting.Indented, false));
The documentation for WriteRawValue()
states:
Writes raw JSON where a value is expected and updates the writer's state.
Whereas the documentation WriteRaw()
states:
Writes raw JSON without changing the writer's state.
The failure to advance the writer's state explains why an exception is thrown when an attempt is made to write subsequent content.
That being said, you're creating a lot of unnecessary intermediate string
, Stream
and JObject
representations inside your converter. A simpler approach would be, in WriteJson()
to:
Construct an XDocument
and write the DataSet
directly to it using XContainer.CreateWriter()
;
Serialize the XDocument
directly to the incoming JsonWriter
by constructing a local XmlNodeConverter
.
Serialization would follow the reverse process. Thus your DataSetConverter
would look like:
class DataSetConverter : JsonConverter<DataSet>
{
public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.MoveToContent().TokenType == JsonToken.Null)
return null;
var converter = new XmlNodeConverter { OmitRootObject = false };
var document = (XDocument)converter.ReadJson(reader, typeof(XDocument), existingValue, serializer);
using (var xmlReader = document.CreateReader())
{
var dataSet = existingValue ?? new DataSet();
dataSet.ReadXml(xmlReader);
return dataSet;
}
}
public override void WriteJson(JsonWriter writer, DataSet dataSet, JsonSerializer serializer)
{
var document = new XDocument();
using (var xmlWriter = document.CreateWriter())
{
dataSet.WriteXml(xmlWriter, XmlWriteMode.WriteSchema);
}
var converter = new XmlNodeConverter { OmitRootObject = false };
converter.WriteJson(writer, document, serializer);
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
// Start up the reader if not already reading, and skip comments
if (reader.TokenType == JsonToken.None)
reader.Read();
while (reader.TokenType == JsonToken.Comment && reader.Read())
{}
return reader;
}
}
Notes:
You are inheriting from JsonConverter<DataSet>
, and in ReadJson()
you construct an object of type DataSet
directly. However, as shown in the reference source, JsonConverter<T>.CanConvert(Type objectType)
applies to all subclasses of the type T
also:
public sealed override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
Thus you may need to override CanConvert
and make it apply only when the object type is equal to typeof(DataSet)
-- but since the method has been sealed, you cannot. Therefore, it may prove necessary to inherit from the non-generic base class JsonConverter
instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With