json.net: DateTimeStringConverter gets an already DateTime converted object in ReadJson() - c#

Prerequisites:
JSON.Net 11.0.2
I need to store the UTC DateTime round-trip date/time pattern via a JSON based REST-API.
string utcTimestamp = DateTime.UtcNow.ToString( "o" );
// 2018-11-27T22:35:32.1234567Z
So I wrote myself a DateTimeStringConverter to ensure no local culture information gets involved.
class DateTimeStringConverter:
JsonConverter<DateTime>
{
public override void WriteJson( JsonWriter writer, DateTime value, JsonSerializer serializer )
{
string convertedValue = value.ToString( "o" );
writer.WriteValue( convertedValue );
}
public override DateTime ReadJson( JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer )
{
string value = reader.Value.ToString( );
DateTime convertedValue = DateTime.Parse( value ).ToLocalTime( );
return convertedValue;
}
}
I was very confused as to why I was getting a DateTime object without the milliseconds. After a lot of trial and error, I got into it.
public override DateTime ReadJson( JsonReader reader, Type objectType, DateTime existingValue, bool hasExistingValue, JsonSerializer serializer )
{
Console.WriteLine( reader.Value.GetType( ).ToString( ) );
// System.DateTime
string value = reader.Value.ToString( );
DateTime convertedValue = DateTime.Parse( value ).ToLocalTime( );
return convertedValue;
}
JSON.Net serves me an already converted DateTime object to deserialize on my own without any millisecond data. I didn't find any clues as to whether it is a bug or a feature.
To counter check it I wrote a BooleanStringConverter.
class BoolStringConverter:
JsonConverter<bool>
{
public override void WriteJson( JsonWriter writer, bool value, JsonSerializer serializer )
{
string convertedValue = false == value ? "False" : "True";
writer.WriteValue( convertedValue );
}
public override bool ReadJson( JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer )
{
Console.WriteLine( reader.Value.GetType( ).ToString( ) );
// System.String
string value = ( string ) reader.Value;
bool convertedValue = "False" == value ? false : true;
return convertedValue;
}
}
JSON.Net doesn't serve me an already converted bool object.
Is it a bug or is it a feature?

This is a known behavior of Json.Net. Since JSON does not have a built-in syntax for denoting dates (like it does for booleans), they have to be represented as strings. By default Json.Net tries to be nice and parse date-looking strings for you.
If you are using your own converter for dates, or otherwise want to handle date parsing yourself, you need to be sure to set the DateParseHandling setting to None in the JsonSerializerSettings, otherwise the internal reader will try to handle it first.
JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new DateTimeStringConverter() },
DateParseHandling = DateParseHandling.None
};
var foo = JsonConvert.DeserializeObject<Foo>(json, settings);

We can cast the DateTime value directly to DateTimeOffset, like so: (DateTimeOffset) reader.Value;.
Full code:
public class DateTimeConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTimeOffset);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null) return null;
var deviceTime = (DateTimeOffset) reader.Value;
return deviceTime.DateTime;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue((DateTime)value);
}
}

Related

How to deserialize EPOCH using Newtonsoft

I need to deserialize a JSON using Newtonsoft.Json, one of my values in my JSON has an EPOCH format:
*"fechaGrabacionODH": {
"$date": 1634689986082
},*
Im using this to Deserialize, but it's not working
*var settings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.MicrosoftDateFormat,
};*
*Data = JsonConvert.DeserializeObject<Constructores_IdPnODH.Data>(payload_Decryp, settings);**
I'm getting this error:
Unexpected character encountered while parsing value: {. Path 'fechaGrabacionODH', line 1, position 261.
I really appreciate any help.
Thanks in advance.
You can use a custom JsonConverter
public class EpochConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var jObject = new JObject();
jObject["$date"] = new DateTimeOffset((DateTime)value).ToUnixTimeMilliseconds();
jObject.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var epoch = jObject.GetValue("$date").Value<long>();
return DateTimeOffset.FromUnixTimeMilliseconds(epoch).UtcDateTime;
}
public override bool CanRead => true;
public override bool CanConvert(Type objectType) => objectType == typeof(DateTime);
}
And then apply it with an attribute
class YourObject{
[JsonConverter(typeof(EpochConverter))]
public DateTime fechaGrabacionODH;
}
dotnetfiddle

Force JSON.NET to include milliseconds AND output null for a null date field when serializing DateTime

I am having trouble with DateTimeFormat in Newtonsoft.Json. The API that I am publishing to requires milliseconds on all of the dates. The object I am serializing has a number of date fields, and some of these are null for some of the data.
I can't seem to get the JSON to output both null for the null dates AND inlucde the milliseconds in the fields that have dates. I can get one or other working, but not both together.
Pretty sure it is something small, but I've not managed to get to the bottom of it and I've spent a few days at it.
This correctly outputs null, but does not include milliseconds:
public string ToJson()
{
return JsonConvert.SerializeObject(this, new MinDateTimeConverter()).ToString();
}
public class MinDateTimeConverter : IsoDateTimeConverter
{
public MinDateTimeConverter()
{
DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'";
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return DateTime.MinValue;
return (DateTime)reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateTime dateTimeValue = (DateTime)value;
if (dateTimeValue == DateTime.MinValue)
{
writer.WriteNull();
return;
}
writer.WriteValue(value);
}
}
This is the output:
"dateOfBirth": "1943-03-02T00:00:00Z",
"dateOfDeath": null`
This correctly outputs dates including milliseconds, but null dates are converted to a DateTime as well:
public string ToJson()
{
return JsonConvert.SerializeObject(this, new IsoDateTimeConverter() { DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'" }).ToString();
}
This is the output:
"dateOfBirth": "1943-03-02T00:00:00.000Z",
"dateOfDeath": "0001-01-01T00:00:00.000Z"
I've looked at both the below SO articles but can't get them to work together.
Force JSON.NET to include milliseconds when serializing DateTime (even if ms component is zero)
JSON.NET Serialize DateTime.MinValue as null
Any help greatly appreciated.
The issue is that the ReadJson and WriteJson methods in your MinDateTimeConverter do not call the base class, so the normal processing that handles the custom date format in the non-null case never happens. Here is the corrected code:
public class MinDateTimeConverter : IsoDateTimeConverter
{
public MinDateTimeConverter()
{
DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fff'Z'";
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(DateTime) && reader.TokenType == JsonToken.Null)
return DateTime.MinValue;
return base.ReadJson(reader, objectType, existingValue, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateTime date && date == DateTime.MinValue)
{
writer.WriteNull();
return;
}
base.WriteJson(writer, value, serializer);
}
}
Fiddle: https://dotnetfiddle.net/x8PAzf

Reading huge integers with Json.NET

I've got some json with huge integers, in the order of a few hundred digits. I'd like to parse those as BouncyCastle's BigInteger (https://github.com/onovotny/BouncyCastle-PCL/blob/pcl/crypto/src/math/BigInteger.cs).
{
"bigNumber":12093812947635091350945141034598534526723049126743245...
}
So I've implemented a converter, using a contract resolver in the default settings.
internal class BigIntegerConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken jToken = JToken.Load(reader);
return new BigInteger(jToken.ToString());
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BigInteger));
}
}
public class BigIntegerContractResolver : DefaultContractResolver
{
private static readonly JsonConverter bigIntegerConverter = new BigIntegerConverter();
private static Type type = typeof(BigInteger);
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == type)
{
return bigIntegerConverter;
}
return base.ResolveContractConverter(objectType);
}
}
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new BigIntegerContractResolver()
};
The writer works as it should, writing a (large) integer value instead of the class BigInteger with all its properties etc. However, the reading fails. Neither ReadJson nor CanConvert appear to be invoked.
I get the following exception:
JsonReaderException: JSON integer 340597435091750914358634185762341897561435984635897436598435643875643189576413589743659817456... is too large or small for an Int64.
How do I get Json.NET to parse this number as a string instead of an integer?
Ideally I don't want to have to parse the json string myself first, to add quotes.
If your large number isn't quoted, Json.Net will deserialize it as a System.Numerics.BigInteger. This happens inside the JsonTextReader, well before the converter gets a chance to handle it. So if you want your result type to be Org.BouncyCastle.Math.BigInteger, you'll need to convert from System.Numerics.BigInteger. (Seems a little backwards, I know. The other alternative is to create your own JsonTextReader, but that is probably going to be more trouble than it is worth -- most of the useful bits of the existing reader are in private or internal methods, so subclassing it is not practical.)
I was able to get this converter to work:
class BigIntegerConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Org.BouncyCastle.Math.BigInteger));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
System.Numerics.BigInteger big = (System.Numerics.BigInteger)reader.Value;
return new Org.BouncyCastle.Math.BigInteger(big.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
}
Here is the test program I used. Note that I did not use a resolver. JsonSerializerSettings has a Converters collection, so I just added the BigIntegerConverter to that.
class Program
{
static void Main(string[] args)
{
string json = #"
{
""bigNumber"": 12093812947635091350945141034598534526723049126743245
}";
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new BigIntegerConverter() }
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json);
Console.WriteLine(foo.BigNumber.ToString());
}
}
class Foo
{
public Org.BouncyCastle.Math.BigInteger BigNumber { get; set; }
}
Output:
12093812947635091350945141034598534526723049126743245
You can try creating object that handles the output of the json like this:
public class YourModel
{
[JsonConverter(typeof(CustomConverter<BigInteger>))]
public BigInteger YourProperty{ get; set; }
}
And now it can become more generic for every type you need:
public class CustomConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
your code ..
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//explicitly specify the concrete type we want to create
return serializer.Deserialize<T>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
your code ...
}
}

Deserialization issue for datetime field

What is the best way to deserialize an JSON
I have the following JSON
"_created" : {
"$dt": "2013-03-26T16:45:20Z"
}
and i want get field of object, like this - DataTime Created {get; set;}
Question for json.net http://james.newtonking.com/projects/json-net.aspx experts
Easiest way is creating JsonConvertor
public class MongoDbDateTimeConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
return jObject["$dt"].Value<DateTime>();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
and used it with property
[JsonConverter(typeof(MongoDbDateTimeConverter))]
public DateTime Created { get; set; }
You could use the JSON serializer/deserializer from NewtonSoft, worked ok for me.
Nuget package
MediaTypeFormatter example
Add following MediaTypeFormatter to the GlobalConfiguration like so:
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonFormatter());
MediaTypeFormatter:
public class JsonFormatter : MediaTypeFormatter
{
private const string WesternEuropeStandardTime = "W. Europe Standard Time";
private TimeZoneInfo timeZoneInfo;
public JsonFormatter()
{
SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
this.timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(WesternEuropeStandardTime);
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
{
Task<object> task = Task<object>.Factory.StartNew(() =>
{
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
};
StreamReader sr = new StreamReader(readStream);
JsonTextReader jreader = new JsonTextReader(sr);
JsonSerializer ser = new JsonSerializer();
ser.Converters.Add(new DateTimeConverter(this.timeZoneInfo) { DateTimeFormat = "o" });
return ser.Deserialize(jreader, type);
});
return task;
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
Task task = Task.Factory.StartNew(() =>
{
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
};
string json = JsonConvert.SerializeObject(
value,
Formatting.Indented,
new JsonConverter[1] { new DateTimeConverter(this.timeZoneInfo) { DateTimeFormat = "o" } });
byte[] buf = System.Text.Encoding.Default.GetBytes(json);
writeStream.Write(buf, 0, buf.Length);
writeStream.Flush();
});
return task;
}
private class DateTimeConverter : IsoDateTimeConverter
{
private TimeZoneInfo timeZoneInfo;
public DateTimeConverter(TimeZoneInfo timeZoneInfo)
{
this.timeZoneInfo = timeZoneInfo;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateTime? date = value as DateTime?;
if (date.HasValue && DateTime.MinValue != date.Value && DateTime.MaxValue != date.Value)
{
TimeSpan timeZoneOffset = this.timeZoneInfo.GetUtcOffset(date.Value);
value = DateTime.SpecifyKind(date.Value - timeZoneOffset, DateTimeKind.Utc);
}
base.WriteJson(writer, value, serializer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = base.ReadJson(reader, objectType, existingValue, serializer);
DateTime? date = result as DateTime?;
if (date.HasValue && DateTime.MinValue != date.Value && DateTime.MaxValue != date.Value)
{
TimeSpan timeZoneOffset = this.timeZoneInfo.GetUtcOffset(date.Value);
result = DateTime.SpecifyKind(date.Value + timeZoneOffset, DateTimeKind.Utc);
}
return result;
}
}
}
if you are using json.NET
try to serialize a datetime field or an object consisting of datetime field like this:
JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.MicrosoftDateFormat
};
string serializedObject= Newtonsoft.Json
.JsonConvert
.SerializeObject(data, microsoftDateFormatSettings);
it works without a glitch, if serialization has been done using JSON.NET :)
and then you can successfully deserialize it back
var myobject = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedObject);
If you're asking about a JSON parser in C#, then that is more of a Google thing. Go search there. There are loads of them available.
If you're asking about how to parse this string into DateTime, then DateTime.TryParse() will help you. You can help it by supplied string format, something like yyyy-MM-ddTHH:mm:ss.

Parsing ISO Duration with JSON.Net

I have a Web API project with the following settings in Global.asax.cs:
var serializerSettings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateTimeZoneHandling = DateTimeZoneHandling.Utc
};
serializerSettings.Converters.Add(new IsoDateTimeConverter());
var jsonFormatter = new JsonMediaTypeFormatter { SerializerSettings = serializerSettings };
jsonFormatter.MediaTypeMappings.Add(GlobalConfiguration.Configuration.Formatters[0].MediaTypeMappings[0]);
GlobalConfiguration.Configuration.Formatters[0] = jsonFormatter;
WebApiConfig.Register(GlobalConfiguration.Configuration);
Despite all this, Json.Net cannot parse ISO durations.
It throws this error:
Error converting value "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z" to
type 'System.TimeSpan'.
I'm using Json.Net v4.5.
I've tried different values such as "P1M" and others listed on the wiki page with no luck.
So the question is:
Am I missing something?
Or do I have to write some custom formatter?
I ran into the same problem and am now using this custom converter to Convert .NET TimeSpans to ISO 8601 Duration strings.
public class TimeSpanConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var ts = (TimeSpan) value;
var tsString = XmlConvert.ToString(ts);
serializer.Serialize(writer, tsString);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
var value = serializer.Deserialize<String>(reader);
return XmlConvert.ToTimeSpan(value);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (TimeSpan) || objectType == typeof (TimeSpan?);
}
}

Categories

Resources