I have data in mongo which is like this:
"trd" : ISODate("2003-12-08T00:00:00Z")
Now, I am doing getting of data from Mongo as BsonDocument like this:
var builder = Builders<BsonDocument>.Filter;
var filter = builder.Eq("wsid", id);
var mongoListBatch = _client.GetManyByFilter(filter, "directory");
JsonWriterSettings settings = new JsonWriterSettings();
settings.OutputMode = JsonOutputMode.Strict;
var lists = mongoListBatch.ToJson(settings);
The problem is that I am expecting to get DateTime object like this inside the json:
"transactiedatum": "23-02-1993"
but instead, im getting:
"transactiedatum": {
"$date": 1070841600000
}
First of all, its unix time format, second, there is change in the json structure. How can I handle this?
We had similar problem. This sounds complicated, but its not that kind of big of a deal.
So, first of all, here is the link to Mongo C# drive on github:
MongoC#Driver
For you, these are 2 important links in there:
Bson Extension Methods
JsonWriter
JsonWriterContext
What we wanted to achieve is that we don't want representation in our Json string to be in unix timestamp format, so we copied these classes into our project with different names, so, here are all of them:
Extension Class:
using System;
using System.IO;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson;
namespace Fishing.MongoDB.Serializers
{
public static class MyJsonWriterSettings
{
/// <summary>
/// Serializes an object to a BSON byte array.
/// </summary>
/// <typeparam name="TNominalType">The nominal type of the object.</typeparam>
/// <param name="obj">The object.</param>
/// <param name="serializer">The serializer.</param>
/// <param name="writerSettings">The writer settings.</param>
/// <param name="configurator">The serialization context configurator.</param>
/// <param name="args">The serialization args.</param>
/// <returns>A BSON byte array.</returns>
public static byte[] ToBson<TNominalType>(
this TNominalType obj,
IBsonSerializer<TNominalType> serializer = null,
BsonBinaryWriterSettings writerSettings = null,
Action<BsonSerializationContext.Builder> configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs)
)
{
return ToBson(obj, typeof(TNominalType), writerSettings, serializer, configurator, args);
}
/// <summary>
/// Serializes an object to a BSON byte array.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="nominalType">The nominal type of the object..</param>
/// <param name="writerSettings">The writer settings.</param>
/// <param name="serializer">The serializer.</param>
/// <param name="configurator">The serialization context configurator.</param>
/// <param name="args">The serialization args.</param>
/// <returns>A BSON byte array.</returns>
/// <exception cref="System.ArgumentNullException">nominalType</exception>
/// <exception cref="System.ArgumentException">serializer</exception>
public static byte[] ToBson(
this object obj,
Type nominalType,
BsonBinaryWriterSettings writerSettings = null,
IBsonSerializer serializer = null,
Action<BsonSerializationContext.Builder> configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
if (nominalType == null)
{
throw new ArgumentNullException("nominalType");
}
if (serializer == null)
{
serializer = BsonSerializer.LookupSerializer(nominalType);
}
if (serializer.ValueType != nominalType)
{
var message = string.Format("Serializer type {0} value type does not match document types {1}.", serializer.GetType().FullName, nominalType.FullName);
throw new ArgumentException(message, "serializer");
}
using (var memoryStream = new MemoryStream())
{
using (var bsonWriter = new BsonBinaryWriter(memoryStream, writerSettings ?? BsonBinaryWriterSettings.Defaults))
{
var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator);
args.NominalType = nominalType;
serializer.Serialize(context, args, obj);
}
return memoryStream.ToArray();
}
}
/// <summary>
/// Serializes an object to a BsonDocument.
/// </summary>
/// <typeparam name="TNominalType">The nominal type of the object.</typeparam>
/// <param name="obj">The object.</param>
/// <param name="serializer">The serializer.</param>
/// <param name="configurator">The serialization context configurator.</param>
/// <param name="args">The serialization args.</param>
/// <returns>A BsonDocument.</returns>
public static BsonDocument ToBsonDocument<TNominalType>(
this TNominalType obj,
IBsonSerializer<TNominalType> serializer = null,
Action<BsonSerializationContext.Builder> configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
return ToBsonDocument(obj, typeof(TNominalType), serializer, configurator, args);
}
/// <summary>
/// Serializes an object to a BsonDocument.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="nominalType">The nominal type of the object.</param>
/// <param name="serializer">The serializer.</param>
/// <param name="configurator">The serialization context configurator.</param>
/// <param name="args">The serialization args.</param>
/// <returns>A BsonDocument.</returns>
/// <exception cref="System.ArgumentNullException">nominalType</exception>
/// <exception cref="System.ArgumentException">serializer</exception>
public static BsonDocument ToBsonDocument(
this object obj,
Type nominalType,
IBsonSerializer serializer = null,
Action<BsonSerializationContext.Builder> configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
if (nominalType == null)
{
throw new ArgumentNullException("nominalType");
}
if (obj == null)
{
return null;
}
if (serializer == null)
{
var bsonDocument = obj as BsonDocument;
if (bsonDocument != null)
{
return bsonDocument; // it's already a BsonDocument
}
var convertibleToBsonDocument = obj as IConvertibleToBsonDocument;
if (convertibleToBsonDocument != null)
{
return convertibleToBsonDocument.ToBsonDocument(); // use the provided ToBsonDocument method
}
serializer = BsonSerializer.LookupSerializer(nominalType);
}
if (serializer.ValueType != nominalType)
{
var message = string.Format("Serializer type {0} value type does not match document types {1}.", serializer.GetType().FullName, nominalType.FullName);
throw new ArgumentException(message, "serializer");
}
// otherwise serialize into a new BsonDocument
var document = new BsonDocument();
using (var bsonWriter = new BsonDocumentWriter(document))
{
var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator);
args.NominalType = nominalType;
serializer.Serialize(context, args, obj);
}
return document;
}
/// <summary>
/// Serializes an object to a JSON string.
/// </summary>
/// <typeparam name="TNominalType">The nominal type of the object.</typeparam>
/// <param name="obj">The object.</param>
/// <param name="writerSettings">The JsonWriter settings.</param>
/// <param name="serializer">The serializer.</param>
/// <param name="configurator">The serializastion context configurator.</param>
/// <param name="args">The serialization args.</param>
/// <returns>
/// A JSON string.
/// </returns>
public static string ToMyJson<TNominalType>(
this TNominalType obj,
JsonWriterSettings writerSettings = null,
IBsonSerializer<TNominalType> serializer = null,
Action<BsonSerializationContext.Builder> configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
return ToMyJson(obj, typeof(TNominalType), writerSettings, serializer, configurator, args);
}
/// <summary>
/// Serializes an object to a JSON string.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="nominalType">The nominal type of the objectt.</param>
/// <param name="writerSettings">The JsonWriter settings.</param>
/// <param name="serializer">The serializer.</param>
/// <param name="configurator">The serialization context configurator.</param>
/// <param name="args">The serialization args.</param>
/// <returns>
/// A JSON string.
/// </returns>
/// <exception cref="System.ArgumentNullException">nominalType</exception>
/// <exception cref="System.ArgumentException">serializer</exception>
public static string ToMyJson(
this object obj,
Type nominalType,
JsonWriterSettings writerSettings = null,
IBsonSerializer serializer = null,
Action<BsonSerializationContext.Builder> configurator = null,
BsonSerializationArgs args = default(BsonSerializationArgs))
{
if (nominalType == null)
{
throw new ArgumentNullException("nominalType");
}
if (serializer == null)
{
serializer = BsonSerializer.LookupSerializer(nominalType);
}
if (serializer.ValueType != nominalType)
{
var message = string.Format("Serializer type {0} value type does not match document types {1}.", serializer.GetType().FullName, nominalType.FullName);
throw new ArgumentException(message, "serializer");
}
using (var stringWriter = new StringWriter())
{
using (var bsonWriter = new JsonWriterMine(stringWriter, writerSettings ?? JsonWriterSettings.Defaults))
{
var context = BsonSerializationContext.CreateRoot(bsonWriter, configurator);
args.NominalType = nominalType;
serializer.Serialize(context, args, obj);
}
return stringWriter.ToString();
}
}
}
}
So basicaly, copy the whole class from github into your own class, and change names of 2 methods: ToJson() into one of yours. Over here you can see that mine are ToJsonMine().
Now, to the second class you will need:
JsonWriter
using MongoDB.Bson.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MongoDB.Bson;
namespace Fishing.MongoDB.Serializers
{
public class JsonWriterMine : BsonWriter
{
// private fields
private TextWriter _textWriter;
private JsonWriterSettings _jsonWriterSettings; // same value as in base class just declared as derived class
private InternalJsonWriterContext _context;
// constructors
/// <summary>
/// Initializes a new instance of the JsonWriter class.
/// </summary>
/// <param name="writer">A TextWriter.</param>
public JsonWriterMine(TextWriter writer)
: this(writer, JsonWriterSettings.Defaults)
{
}
/// <summary>
/// Initializes a new instance of the JsonWriter class.
/// </summary>
/// <param name="writer">A TextWriter.</param>
/// <param name="settings">Optional JsonWriter settings.</param>
public JsonWriterMine(TextWriter writer, JsonWriterSettings settings)
: base(settings)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
_textWriter = writer;
_jsonWriterSettings = settings; // already frozen by base class
_context = new InternalJsonWriterContext(null, ContextType.TopLevel, "");
State = BsonWriterState.Initial;
}
/// <summary>
/// Writes a BSON DateTime to the writer.
/// </summary>
/// <param name="value">The number of milliseconds since the Unix epoch.</param>
public override void WriteDateTime(long value)
{
if (Disposed) { throw new ObjectDisposedException("JsonWriter"); }
if (State != BsonWriterState.Value && State != BsonWriterState.Initial)
{
ThrowInvalidState("WriteDateTime", BsonWriterState.Value, BsonWriterState.Initial);
}
WriteNameHelper(Name);
switch (_jsonWriterSettings.OutputMode)
{
case JsonOutputMode.Strict:
var utcDateTimeFirst = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(value);
_textWriter.Write($"\"{utcDateTimeFirst.ToString("yyyy-MM-ddTHH:mm:ss.FFFZ")}\"");
break;
case JsonOutputMode.Shell:
default:
// use ISODate for values that fall within .NET's DateTime range, and "new Date" for all others
if (value >= BsonConstants.DateTimeMinValueMillisecondsSinceEpoch &&
value <= BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch)
{
var utcDateTime = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(value);
_textWriter.Write("ISODate(\"{0}\")", utcDateTime.ToString("yyyy-MM-ddTHH:mm:ss.FFFZ"));
}
else
{
_textWriter.Write("new Date({0})", value);
}
break;
}
State = GetNextState();
}
}
}
This is where the magic happens. Copy the whole class JsonWriter from GitHub into your own and give it a new name(Extend BsonWriter nad implement all methods). Now, here you can manipulate how do you want your date to be serialized. Change WriteDateTime(long value) accordingly. As you can see, in the case JsonOutputMode.Strict: I changed it to return me a DateTime object formatted the way I need it.
And lastly, since MongoSerializer has the internal class that is called JsonWriterContext, you need to create your own and use it in JsonWriter(step 2).
This is how it looks with me(you can copy it whole):
using MongoDB.Bson.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Fishing.MongoDB.Serializers
{
public class InternalJsonWriterContext
{
// private fields
private InternalJsonWriterContext _parentContext;
private ContextType _contextType;
private string _indentation;
private bool _hasElements = false;
// constructors
internal InternalJsonWriterContext(InternalJsonWriterContext parentContext, ContextType contextType, string indentChars)
{
_parentContext = parentContext;
_contextType = contextType;
_indentation = (parentContext == null) ? indentChars : parentContext.Indentation + indentChars;
}
// internal properties
internal InternalJsonWriterContext ParentContext
{
get { return _parentContext; }
}
internal ContextType ContextType
{
get { return _contextType; }
}
internal string Indentation
{
get { return _indentation; }
}
internal bool HasElements
{
get { return _hasElements; }
set { _hasElements = value; }
}
}
}
After you have everything, you will see in the MongoCursorJsonConverter(1st step), in the second ToJsonMine() method that I have this line:
using (var bsonWriter = new JsonWriterMine(stringWriter, writerSettings ?? JsonWriterSettings.Defaults))
You should just replace it with your own custom class that you created in step 2 and it will work like a charm.
And in the end, you just call:
var lists = mongoListBatch.ToJson(settings);
and it will serialize date like you added inside WriteDate(long value)
Related
2 parts to this question.
I am the owner of an API and if there is no data to return (based on business rules) the response is delivered like so:
var resp = new { error = "", code = (int)HttpStatusCode.OK, data = leads};
var json = JsonConvert.SerializeObject(resp);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
return Ok(json);
And when I call this in Postman, this is rendered as:
"{\"error\":\"\",\"code\":200,\"data\":[]}"
What is with the slashes?
My second part, which may or may not be fixed by fixing the slashes, is when I consume the API, and deserialize the response to an object, I receive the following error
Error converting value "{"error":"","code":200,"data":[]}" to type 'DataTypes.IntegrationResponse'. Path '', line 1, position 43.
For those who need it, IntegrationResponse is:
public class IntegrationResponse
{
public string error { get; set; }
public int code { get; set; }
public List<IntegrationLead> data { get; set; }
}
I'd make this a comment if I could, need more rep. That said -
Try making List<IntegrationLead> an InegrationLead[] and all the slashes are to escape the quotes and you have an awesome name. Cheers!
Here is how i will do this
in APi
public ActionResult SomeActionMethod() {
return Json(new {foo="bar", baz="Blech"});
}
If you want to use JsonConverter and control how the data get serilized then
public class JsonNetResult : ActionResult
{
/// <summary>
/// Initializes a new instance of the <see cref="JsonNetResult"/> class.
/// </summary>
public JsonNetResult()
{
}
/// <summary>
/// Gets or sets the content encoding.
/// </summary>
/// <value>The content encoding.</value>
public Encoding ContentEncoding { get; set; }
/// <summary>
/// Gets or sets the type of the content.
/// </summary>
/// <value>The type of the content.</value>
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the data.
/// </summary>
/// <value>The data object.</value>
public object Data { get; set; }
/// <summary>
/// Enables processing of the result of an action method by a custom type that inherits from the <see cref="T:System.Web.Mvc.ActionResult"/> class.
/// </summary>
/// <param name="context">The context in which the result is executed. The context information includes the controller, HTTP content, request context, and route data.</param>
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrWhiteSpace(this.ContentType) ? this.ContentType : "application/json";
if (this.ContentEncoding != null)
{
response.ContentEncoding = this.ContentEncoding;
}
if (this.Data != null)
{
response.Write(JsonConvert.SerializeObject(this.Data));
}
}
}
Blockquote
public class CallbackJsonResult : JsonNetResult
{
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
public CallbackJsonResult(HttpStatusCode statusCode)
{
this.Initialize(statusCode, null, null);
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="description">The description.</param>
public CallbackJsonResult(HttpStatusCode statusCode, string description)
{
this.Initialize(statusCode, description, null);
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="data">The callback result data.</param>
public CallbackJsonResult(object data, HttpStatusCode statusCode = HttpStatusCode.OK)
{
this.ContentType = null;
this.Initialize(statusCode, null, data);
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackJsonResult"/> class.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="description">The description.</param>
/// <param name="data">The callback result data.</param>
public CallbackJsonResult(HttpStatusCode statusCode, string description, object data)
{
this.Initialize(statusCode, description, data);
}
/// <summary>
/// Initializes this instance.
/// </summary>
/// <param name="statusCode">The status code.</param>
/// <param name="description">The description.</param>
/// <param name="data">The callback result data.</param>
private void Initialize(HttpStatusCode statusCode, string description, object data)
{
Data = new JsonData() { Success = statusCode == HttpStatusCode.OK, Status = (int)statusCode, Description = description, Data = data };
}
}
}
then create an extention
/// <summary>
/// return Json Action Result
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <param name="JsonFormatting"></param>
/// <returns></returns>
public static CallbackJsonResult ViewResult<T>(this T o)
{
return new CallbackJsonResult(o);
}
No APi simple use the extention that you created
public ActionResult SomeActionMethod() {
return new { error = "", code = (int)HttpStatusCode.OK, data = leads}.ViewResult();
}
I am currently writing some code to try to experiment with separating and abstracting two parts of our storage strategies at work. We currently use JSON format stored into a file and then retrieve it as our persistent storage. I am trying to experiment with separating the two concepts:
1) Concept one keeps the serialization separate from the storage type
2) Concept two keeps the storage type separate from the serialization strategy.
I found a good way that works doing some research on various threads, such as using TextWriter/TextReader instead of directly using Files so that any Stream type can be used (FileStream/MemoryStream/etc) so that the unit tests can be done without files. However, I am running into a problem since the TextWriter/TextReader classes which wrap the streams automatically close and dispose of the streams when they are themselves disposed, which is what I want in practice, but gets me stuck in unit testing.
Here is the code I have so far... this is for concept 1, the serialization process. Here are the interfaces for it:
/// <summary>
/// Interface for a serializer which reads from a stream and creates a type
/// </summary>
public interface IInSerializer
{
/// <summary>
/// Load type from a stream
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
bool Load(TextReader reader);
}
/// <summary>
/// Interface for writing a type out into a stream
/// </summary>
public interface IOutSerializer
{
/// <summary>
/// Save to the stream
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
bool Save(TextWriter writer);
}
/// <summary>
/// Helper interface which provides interface <see cref="IInSerializer"/>
/// and <see cref="IOutSerializer"/> for both reading/writing
/// </summary>
public interface IInOutSerializer : IInSerializer, IOutSerializer
{
}
Here is an abstract implementation of the serializer for JSON format:
/// <summary>
/// Implementation of <see cref="IInOutSerializer"/> which serializes into JSON format
/// </summary>
/// <typeparam name="T">Type to be serialized</typeparam>
public abstract class JSONSerializer<T> : IInOutSerializer
{
/// <summary>
/// Source of serialization
/// </summary>
public T Source { get; set; }
/// <summary>
/// Provided by very specific type to load the Jobject into type T
/// </summary>
/// <param name="jObject"></param>
/// <returns></returns>
protected abstract bool LoadJObject(JObject jObject);
/// <summary>
/// Provided by very specific type to save type T into a Jobject
/// </summary>
/// <param name="jObject"></param>
/// <returns></returns>
protected abstract bool Serialize(JObject jObject);
/// <summary>
/// <see cref="IInOutSerializer.Load"/>
/// </summary>
/// <param name="reader"></param>
/// <returns></returns>
public bool Load(TextReader reader)
{
using (var json = new JsonTextReader(reader))
{
var jObject = JToken.ReadFrom(json) as JObject;
if (jObject != null)
return LoadJObject(jObject);
}
return false;
}
/// <summary>
/// <see cref="IInOutSerializer.Save"/>
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
public bool Save(TextWriter writer)
{
var jObject = new JObject();
if (Serialize(jObject))
{
using (var json = new JsonTextWriter(writer))
{
json.Formatting = Formatting.Indented;
jObject.WriteTo(json);
return true;
}
}
return false;
}
}
And here is one of the concrete types for serializing my class MetroLineDetails:
public class MetroLineJSONSerializationStrategy : JSONSerializer<MetroLineDetails>
{
private class MetroLineHelper : IMetroLine, IMetroLineWritable
{
public string DestinationStation
{
get;
set;
}
public Color LineColor
{
get;
set;
}
public char LineLetter
{
get;
set;
}
public string Name
{
get;
set;
}
public bool SaturdayService
{
get;
set;
}
public string SourceStation
{
get;
set;
}
public bool SundayHolidayService
{
get;
set;
}
public static explicit operator MetroLineDetails(MetroLineHelper source)
{
return new MetroLineDetails(source.Name, source.LineColor, source.SourceStation, source.DestinationStation, source.SaturdayService, source.SundayHolidayService);
}
}
protected override bool LoadJObject(JObject jObject)
{
var helper = new MetroLineHelper();
jObject.Read(nameof(MetroLineDetails.Name), (t) => (string)t, (v) => helper.Name = v);
jObject.Read(nameof(MetroLineDetails.LineLetter), (t) => (char)t, (v) => helper.LineLetter = v);
jObject.Read(nameof(MetroLineDetails.SourceStation), (t) => (string)t, (v) => helper.SourceStation = v);
jObject.Read(nameof(MetroLineDetails.DestinationStation), (t) => (string)t, (v) => helper.DestinationStation = v);
jObject.Read(nameof(MetroLineDetails.SaturdayService), (t) => (bool)t, (v) => helper.SaturdayService = v);
jObject.Read(nameof(MetroLineDetails.SundayHolidayService), (t) => (bool)t, (v) => helper.SundayHolidayService = v);
var color = jObject.Read(nameof(MetroLineDetails.LineColor), (t) => (JObject)t);
helper.LineColor = color.ToColor();
Source = (MetroLineDetails)helper;
return true;
}
protected override bool Serialize(JObject jObject)
{
jObject.Add(nameof(MetroLineDetails.Name), Source.Name);
jObject.Add(nameof(MetroLineDetails.LineLetter), Source.LineLetter);
jObject.Add(nameof(MetroLineDetails.SourceStation), Source.SourceStation);
jObject.Add(nameof(MetroLineDetails.DestinationStation), Source.DestinationStation);
jObject.Add(nameof(MetroLineDetails.SaturdayService), Source.SaturdayService);
jObject.Add(nameof(MetroLineDetails.SundayHolidayService), Source.SundayHolidayService);
jObject.Add(nameof(MetroLineDetails.LineColor), Source.LineColor.ToJObject());
return true;
}
}
And now here are my storage type interfaces:
/// <summary>
/// Interface for the storage medium
/// </summary>
public interface IStorageMedium
{
/// <summary>
/// Save the information in the serializer
/// </summary>
/// <param name="serializer"></param>
void Save(IOutSerializer serializer);
/// <summary>
/// Load the information to the serializer
/// </summary>
/// <param name="serializer"></param>
void Load(IInSerializer serializer);
}
And the type specifically for files:
/// <summary>
/// Implementation of <see cref="IStorageMedium"/> which stores into a file
/// </summary>
public class FileStorageMedium : IStorageMedium
{
private readonly string _fileName;
public FileStorageMedium(string fileName)
{
_fileName = fileName;
}
public void Save(IOutSerializer serializer)
{
using (var stream = new FileStream(_fileName, FileMode.Truncate))
{
using (var writer = new StreamWriter(stream))
{
serializer.Save(writer);
}
}
}
public void Load(IInSerializer serializer)
{
using (var stream = new FileStream(_fileName, FileMode.Open))
{
using (var reader = new StreamReader(stream))
{
serializer.Load(reader);
}
}
}
}
As you can see in each layer I want to follow best practices and make sure each method closes and flushes the stream for the caller and not leave things open for the sake of unit testing (I know I could probably change the code to not close the streams, but I don't think that is appropriate).
So, now, using the ideas I've found on the forums to not have anything tied specifically to file streams to help with unit testing, I'm still running into problems finding the best way to unit test this. Here is the unit test I am trying to write:
[TestClass]
public class MetroLine
{
[TestMethod]
public void TestSerialize()
{
var serializer = new MetroLineJSONSerializationStrategy();
serializer.Source = new MetroLineDetails("A", Colors.Blue, "LA Union Station", "San Bernardino", true, true);
using (var stream = new MemoryStream())
{
var writer = new StreamWriter(stream);
serializer.Save(writer);
stream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(stream))
{
var text = reader.ReadToEnd();
}
}
}
}
The stream is closed no matter what I do in the serializer.Save() call since that method uses a disposable which closes the stream (as I believe it should to prevent leaks). The problem is, I can no longer unit test the stream in any way to test whether any of this works. I get exceptions thrown saying you cannot access closed streams anymore, which makes sense. But how can I test the contents of my stream in any meaningful way?
I found GetBuffer on the MemoryStream which allows me to convert the raw buffer into a string and I can unit test the actual JSON blob however I want... here is what I wrote:
[TestMethod]
public void TestSerialize()
{
var serializer = new MetroLineJSONSerializationStrategy();
serializer.Source = new MetroLineDetails("Inland Empire Line", Colors.Blue, 'A', "LA Union Station", "San Bernardino", true, true);
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
serializer.Save(writer);
}
var bytes = stream.GetBuffer();
var json = System.Text.Encoding.UTF8.GetString(bytes);
Assert.AreEqual('{', json[0]);
}
}
My hopes are someone will find this useful!
How do I use a Swagger API (JSON) to C# code generator like SwaggerHub or NSwagStudio to generate C# Client Code for a larger API such as Clio? (https://app.clio.com/api_v4.json).
These tools seem to work fine for smaller APIs, but when you put a large schema in they output code which does not compile and seems to have multiple issues.
/// <summary>Return the data for all triggers</summary>
/// <param name="x_API_VERSION">The [API minor version](#section/Minor-Versions). Default: latest version.</param>
/// <param name="x_BULK">An indicator if [bulk actions](#section/Bulk-Actions) should be performed.
/// When performing a bulk action, the id path parameter is not required.</param>
/// <param name="fields">The fields to be returned. See response samples for what fields are available. For more information see the [fields section](#section/Fields).</param>
/// <param name="is_requirements_required">Filter JurisdictionsToTrigger records to those which require addition requirements to be checked (usually specifying trigger time).</param>
/// <param name="is_served">Filter JurisdictionsToTrigger records to those which require a service type to be selected.</param>
/// <param name="jurisdiction_id">The unique identifier for the Jurisdiction.</param>
/// <param name="limit">A limit on the number of JurisdictionsToTrigger records to be returned. Limit can range between 1 and 200. Default: `200`.</param>
/// <param name="page_token">A token specifying which page to return.</param>
/// <param name="query">Wildcard search for `description` matching a given string.</param>
/// <returns>Ok</returns>
/// <exception cref="ClioAPIException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<JurisdictionsToTriggerList> CourtRulesJurisdictionsId}Triggers.jsonAsync(string x_API_VERSION, bool? x_BULK, string fields, bool? is_requirements_required, bool? is_served, int jurisdiction_id, int? limit, string page_token, string query)
{
return CourtRulesJurisdictionsId}Triggers.jsonAsync(x_API_VERSION, x_BULK, fields, is_requirements_required, is_served, jurisdiction_id, limit, page_token, query, System.Threading.CancellationToken.None);
}
For instance, in the above routine, it adds a "}" to the name CourtRulesJurisdictions}Triggers
I have tried both SwaggerHub and NSwagStudio for this particular API and neither work. The NSwagStudio has the above issue and the SwaggerHub generates code which has this issue. At the end of a client API call to get data, this call to the JsonConvert.DeserializeObject fails. The data is in the Response. Content as I can see it in the debugger, and the type is set to the correct model, but no data is placed in the model.
try
{
return JsonConvert.DeserializeObject(response.Content, type, serializerSettings);
}
catch (Exception e)
{
throw new ApiException(500, e.Message);
}
I reduced the code to this, which doesn't even use anything but the generated model and it fails.
Dim x = "{""data"":{""name"":""Fred G. Jones"",""last_name"":""Jones"",""id"":345171548}}"
u = Newtonsoft.Json.JsonConvert.DeserializeObject(x, GetType(Clio_API.Model.UserShow))
This is the generated model UserShow
using System;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.ComponentModel.DataAnnotations;
using SwaggerDateConverter = Clio_API.Client.SwaggerDateConverter;
namespace Clio_API.Model
{
/// <summary>
/// UserShow
/// </summary>
[DataContract]
public partial class UserShow : IEquatable<UserShow>, IValidatableObject
{
/// <summary>
/// Initializes a new instance of the <see cref="UserShow" /> class.
/// </summary>
[JsonConstructorAttribute]
protected UserShow() { }
/// <summary>
/// Initializes a new instance of the <see cref="UserShow" /> class.
/// </summary>
/// <param name="Data">User Object Response (required).</param>
public UserShow(User Data = default(User))
{
// to ensure "Data" is required (not null)
if (Data == null)
{
throw new InvalidDataException("Data is a required property for UserShow and cannot be null");
}
else
{
this.Data = Data;
}
}
/// <summary>
/// User Object Response
/// </summary>
/// <value>User Object Response</value>
[DataMember(Name="data", EmitDefaultValue=false)]
public User Data { get; set; }
/// <summary>
/// Returns the string presentation of the object
/// </summary>
/// <returns>String presentation of the object</returns>
public override string ToString()
{
var sb = new StringBuilder();
sb.Append("class UserShow {\n");
sb.Append(" Data: ").Append(Data).Append("\n");
sb.Append("}\n");
return sb.ToString();
}
/// <summary>
/// Returns the JSON string presentation of the object
/// </summary>
/// <returns>JSON string presentation of the object</returns>
public string ToJson()
{
return JsonConvert.SerializeObject(this, Formatting.Indented);
}
/// <summary>
/// Returns true if objects are equal
/// </summary>
/// <param name="input">Object to be compared</param>
/// <returns>Boolean</returns>
public override bool Equals(object input)
{
return this.Equals(input as UserShow);
}
/// <summary>
/// Returns true if UserShow instances are equal
/// </summary>
/// <param name="input">Instance of UserShow to be compared</param>
/// <returns>Boolean</returns>
public bool Equals(UserShow input)
{
if (input == null)
return false;
return
(
this.Data == input.Data ||
(this.Data != null &&
this.Data.Equals(input.Data))
);
}
/// <summary>
/// Gets the hash code
/// </summary>
/// <returns>Hash code</returns>
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hashCode = 41;
if (this.Data != null)
hashCode = hashCode * 59 + this.Data.GetHashCode();
return hashCode;
}
}
/// <summary>
/// To validate all properties of the instance
/// </summary>
/// <param name="validationContext">Validation context</param>
/// <returns>Validation Result</returns>
IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
{
yield break;
}
}
}
I'm writting a Wp8/C# library for querying REST Api of MongoLab.
I have an abtract object like this :
[DataContract]
public abstract class Entity
{
[DataMember(Name = "_id")]
public string _id { get; set; }
}
The field _id is autogenerate by Mongo as a ObjectId. But with WP8, I don't have the mongoDb C# driver... The serialization and deserialization doesn't work....
This is what I've tried :
var str = url;
var response = await _httpClient.GetAsync(str);
var rep = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(rep);
I've tried with Datacontractjsonserializer too.
How can I do that?
Thank you
Here is a class I wrote for dealing with JSON serialization and deserialization in .NET 3.5
Don't forget to add a reference to System.ServiceModel.Web.dll
You can use JsonTools.ObjectToJsonString(rep);
using System;
using System.Text;
using System.Runtime.Serialization.Json;
using System.IO;
namespace Utilities
{
/// <summary>
/// Group of static methods for dealing with JSON.
/// </summary>
public static class JsonTools
{
/// <summary>
/// Serializes an object to JSON string.
/// </summary>
/// <param name="obj">The object to serialize. </param>
/// <returns></returns>
/// <exception cref="System.Runtime.Serialization.InvalidDataContractException"></exception>
/// <exception cref="System.Runtime.Serialization.SerializationException"></exception>
/// <exception cref="System.ServiceModel.QuotaExceededExceptionn"></exception>
public static string ObjectToJsonString(object obj)
{
try
{
MemoryStream jsonStream = new MemoryStream();
DataContractJsonSerializer js = new DataContractJsonSerializer(obj.GetType());
js.WriteObject(jsonStream, obj);
jsonStream.Position = 0;
StreamReader sr = new StreamReader(jsonStream);
return sr.ReadToEnd();
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Serializes an object to JSON byte array.
/// </summary>
/// <param name="obj">The object to serialize. </param>
/// <returns></returns>
/// <exception cref="System.Runtime.Serialization.InvalidDataContractException"></exception>
/// <exception cref="System.Runtime.Serialization.SerializationException"></exception>
/// <exception cref="System.ServiceModel.QuotaExceededExceptionn"></exception>
public static byte[] ObjectToJsonByteArray(object obj)
{
try
{
MemoryStream jsonStream = new MemoryStream();
DataContractJsonSerializer js = new DataContractJsonSerializer(obj.GetType());
js.WriteObject(jsonStream, obj);
jsonStream.Position = 0;
return jsonStream.ToArray();
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Deserializes a JSON formatted string to an object of the defined type
/// </summary>
/// <param name="jsonString">JSON formatted string</param>
/// <param name="objType">The type of the object which the jsonString is to be Deserialized to.</param>
/// <returns>Deserialized object</returns>
/// <exception cref="System.Runtime.Serialization.SerializationException"></exception>
public static object JsonStringToObject(string jsonString, Type objType)
{
try
{
DataContractJsonSerializer js = new DataContractJsonSerializer(objType);
byte[] jsonBytes = Encoding.Default.GetBytes(jsonString);
MemoryStream jsonStream = new MemoryStream(jsonBytes);
return js.ReadObject(jsonStream);
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// Deserializes a JSON formatted byte array to an object of the defined type
/// </summary>
/// <param name="jsonBytes">JSON formatted byte array</param>
/// <param name="objType">The type of the object which the jsonString is to be Deserialized to.</param>
/// <returns>Deserialized object</returns>
/// <exception cref="System.Runtime.Serialization.SerializationException"></exception>
public static object JsonByteArrayToObject(byte[] jsonBytes, Type objType)
{
try
{
DataContractJsonSerializer js = new DataContractJsonSerializer(objType);
MemoryStream jsonStream = new MemoryStream(jsonBytes);
return js.ReadObject(jsonStream);
}
catch (Exception)
{
throw;
}
}
}
}
I have an existing class for serializing and deserializing objects to/from XML. It's a generic class with a single type parameter T whose only constraint is where T : IXmlSerializable. However, I want to still be able to use this class on classes that do not implement IXmlSerializable but have the [Serializable] attribute. How could I go about doing this?
From my generic class:
public static class XmlSerializationUtils<T> where T : IXmlSerializable
{
public static T DeserializeXml(XmlDocument xml) { ... }
public static XmlDocument SerializeToXml(T toSerialize) { ... }
}
I found this discussion but there was no solution given, just that I can't do where T : Serializable. Trying to do where T : SerializableAttribute makes Visual Studio say "Cannot use sealed class 'System.SerializableAttribute' as type parameter constraint".
Edit: based on Stephen's answer, I removed the constraints on XmlSerializationUtils<T> and added this static constructor:
static XmlSerializationUtils()
{
Type type = typeof(T);
bool hasAttribute = null != Attribute.GetCustomAttribute(type,
typeof(SerializableAttribute));
bool implementsInterface =
null != type.GetInterface(typeof(IXmlSerializable).FullName);
if (!hasAttribute && !implementsInterface)
{
throw new ArgumentException(
"Cannot use XmlSerializationUtils on class " + type.Name +
" because it does not have the Serializable attribute " +
" and it does not implement IXmlSerializable"
);
}
}
You can check to see if a type is serializable using the IsSerializable property of the Type of the object.
myObj.GetType().IsSerializable
As mentioned, this isn't possible to add as a generic constraint, but would most likely be checked in a constructor.
You can't require an attribute as part of generics. However, you could provide a static constructor that checks for it and throws if it's not found.
I'd just eliminate the type constraint and catch the SerializationException when the type does not serialize or deserialize properly... In fact, this allows your generic Serialize and Deserialize methods to accept a formatter
public enum Formatter { Binary, Xml }
that could control whether the serialization is binary or Xml
public class Serialization
{
public enum Formatter { Binary, Xml }
#region Serialization methods
public static void Serialize2File<T>(T obj, string pathSpec,
Formatter formatter)
{
try
{
switch (formatter)
{
case (Formatter.Binary):
using (var fs = new FileStream(pathSpec, FileMode.Create,
FileAccess.Write, FileShare.Write))
(new BinaryFormatter()).Serialize(fs, obj);
break;
case (Formatter.Xml):
var serializer = new XmlSerializer(typeof(T));
TextWriter textWriter = new StreamWriter(pathSpec);
serializer.Serialize(textWriter, obj);
textWriter.Close();
break;
default:
throw new MyCustomException("Invalid Formatter option");
}
}
catch (SerializationException sX)
{
var errMsg = String.Format(
"Unable to serialize {0} into file {1}",
obj, pathSpec);
throw new MyCustomException(errMsg, sX);
}
}
public static T DeSerializeFromFile<T>(string pathSpec,
Formatter formatter) where T : class
{
try
{
switch (formatter)
{
case (Formatter.Binary):
using (var strm = new FileStream(pathSpec,
FileMode.Open, FileAccess.Read))
{
IFormatter fmt = new BinaryFormatter();
var o = fmt.Deserialize(strm);
if (!(o is T))
throw new ArgumentException("Bad Data File");
return o as T;
}
case (Formatter.Xml):
var serializer = new XmlSerializer(typeof(T));
TextReader rdr = new StreamReader(pathSpec);
return (T)serializer.Deserialize(rdr);
default:
throw new MyCustomException("Invalid Formatter option");
}
}
catch (SerializationException sX)
{
var errMsg = String.Format(
"Unable to deserialize {0} from file {1}",
typeof(T), pathSpec);
throw new MyCustomException(errMsg, sX);
}
}
#endregion Serialization methods
}
C# generic serialization utility class.
The core of serializing classes that implement the IXmlSerializable or Serializable attribute is to check the given type before passing it to the serializer.
XmlDocument Serialize<T>(T obj)
{
Type type = typeof(T);
if (type.HasAttribute<SerializableAttribute>()
| type.ImplementsInterface<IXmlSerializableAttribute>())
return XmlSerializer<T>.Serialize(obj);
throw new InvalidOperationException("Unserializable object given.");
}
To do this, you should implement the following extension methods and a generic serializer.
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Text;
using System.Xml.Schema;
namespace System
{
public static partial class Extensions
{
/// <summary>
/// Checks if the object <see cref="Type"/> has the specified attribute.
/// </summary>
/// <param name="type">
/// Object <see cref="Type"/> for which you want to check whether
/// whether it has <see cref="Attribute"/> specified by the <typeparamref name="T"/>.
/// </param>
public static bool HasAtribute<T>(this Type type) where T : Attribute
{
return type.GetCustomAttributes(typeof(T), true).Any();
}
/// <summary>
/// Checks if the object <see cref="Type"/> implements the specified interface.
/// </summary>
/// <param name="type">
/// Object <see cref="Type"/> for which you want to check whether
/// whether it implements the interface specified by the <paramref name="interfaceType"/> parameter.
/// </param>
/// <param name="interfaceType">
/// The <see cref="Type"/> being tested, which is an interface.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="type"/> is not a class, value type, or interface,
/// and also if <paramref name="interfaceType"/> is not an interface.
/// </exception>
/// <exception cref="ArgumentException">
/// The value <see langword="null"/> was passed as one of the parameters.
/// </exception>
/// <returns>
/// <see langword="true"/> if the object <see cref="Type"/> implements the specified interface.
/// </returns>
public static bool ImplementsInterface(this Type type, Type interfaceType)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (interfaceType == null)
throw new ArgumentNullException(nameof(interfaceType));
if (!interfaceType.IsInterface)
throw new ArgumentException("Argument must be interface.",
nameof(interfaceType));
while (type != null)
{
Type[] interfaces = type.GetInterfaces();
{
int length = interfaces.Length;
for (int i = 0; i < length; i++)
{
if (interfaces[i] == interfaceType || interfaces[i].ImplementsInterface(interfaceType))
return true;
}
}
type = type.BaseType;
}
return false;
}
/// <summary>
/// Checks if the object <see cref="Type"/> implements the specified interface.
/// </summary>
/// <typeparam name="T">
/// The type being checked, which is an interface.
/// </typeparam>
/// <param name="type">
/// Object <see cref="Type"/> for which you want to check whether
/// whether it implements the specified interface <typeparamref name="T"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the object is <see cref="Type"/>
/// implements the <typeparamref name="T"/> interface.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="type"/> is not a class, value type, or interface.
/// </exception>
/// <exception cref="ArgumentException">
/// The value <see langword="null"/> was passed as <paramref name="type"/>.
/// </exception>
public static bool ImplementsInterface<T>(this Type type) where T : class =>
ImplementsInterface(type, typeof(T));
}
}
namespace System.Xml.Serialization
{
/// <summary>
/// Serializes and deserializes <typeparamref name="T"/> objects into XML documents.
/// Allows you to control the process of encoding objects in XML.
/// </summary>
/// <typeparam name="T">Object type.</typeparam>
public static class XmlSerializer<T>
{
private static readonly XmlSerializer _serializer = new XmlSerializer(typeof(T));
private static readonly XmlWriterSettings _defaultWriterSettings = new XmlWriterSettings
{
CheckCharacters = false,
CloseOutput = false,
ConformanceLevel = ConformanceLevel.Auto,
Encoding = DefaultEncoding,
Indent = true,
IndentChars = "\t",
NamespaceHandling = NamespaceHandling.OmitDuplicates,
NewLineChars = "\r\n",
NewLineHandling = NewLineHandling.Replace,
NewLineOnAttributes = false,
OmitXmlDeclaration = false
};
private static readonly XmlReaderSettings _defaultReaderSettings = new XmlReaderSettings
{
CheckCharacters = false,
CloseInput = false,
ConformanceLevel = ConformanceLevel.Auto,
DtdProcessing = DtdProcessing.Prohibit,
IgnoreComments = true,
IgnoreProcessingInstructions = true,
IgnoreWhitespace = true,
LineNumberOffset = 0,
LinePositionOffset = 0,
MaxCharactersFromEntities = 0,
MaxCharactersInDocument = 0,
NameTable = null,
ValidationFlags = XmlSchemaValidationFlags.None,
ValidationType = ValidationType.None,
XmlResolver = null
};
/// <summary>
/// Default character encoding.
/// </summary>
public static Encoding DefaultEncoding => Encoding.UTF8;
/// <summary>
/// Default settings for the <see cref="XmlWriter" /> instance being created.
/// </summary>
public static XmlWriterSettings DefaultXmlWriterSettings => _defaultWriterSettings.Clone();
/// <summary>
/// Default settings for the <see cref="XmlReader" /> instance that is created.
/// </summary>
public static XmlReaderSettings DefaultXmlReaderSettings => _defaultReaderSettings.Clone();
/// <summary>
/// Serializes the given object and returns an XML document.
/// </summary>
/// <param name="o">
/// An instance <typeparamref name="T"/> to serialize.
/// </param>
/// <param name="settings">
/// Settings for the new <see cref="XmlWriter" /> instance.
/// If <see langword="null"/> is specified,
/// settings are used <see cref="DefaultXmlWriterSettings"/>.
/// </param>
/// <returns>An instance of <see cref="XmlDocument"/> that represents given object.</returns>
public static XmlDocument Serialize(T o, XmlWriterSettings settings = null)
{
StringBuilder sb = new StringBuilder();
using (XmlWriter xmlWriter = XmlWriter.Create(sb, settings ?? DefaultXmlWriterSettings))
_serializer.Serialize(xmlWriter, o, (XmlSerializerNamespaces)null);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(sb.ToString());
return xmlDocument;
}
/// <summary>
/// Deserializes the object contained in the specified XML document.
/// </summary>
/// <param name="xmlDocument">
/// An XML document containing the serialized data.
/// </param>
/// <param name="settings">
/// Settings for the new <see cref="XmlReader" /> instance.
/// If <see langword="null"/> is specified,
/// settings are used <see cref="DefaultXmlReaderSettings"/>.
/// </param>
/// <returns> The deserialized object of type <typeparamref name="T"/>. </returns>
public static T Deserialize(XmlDocument xmlDocument, XmlReaderSettings settings)
{
string text = xmlDocument.OuterXml;
using (StringReader reader = new StringReader(text))
using (XmlReader xmlReader = XmlReader.Create(reader, DefaultXmlReaderSettings))
return (T)_serializer.Deserialize(xmlReader);
}
/// <summary>
/// Returns a value indicating whether this <see cref="XmlSerializer" /> can deserialize the specified XML document.
/// </summary>
/// <param name="xmlReader">
/// <see cref="XmlReader" /> Pointing to the document to deserialize.
/// </param>
/// <returns>
/// <see langword="true" /> If this <see cref="XmlSerializer" /> can deserialize an object, <see cref="XmlReader" /> indicates; otherwise, <see langword="false" />.
/// </returns>
public static bool CanDeserialize(XmlReader xmlReader)
{
return _serializer.CanDeserialize(xmlReader);
}
}
}