Querying JSON with Newtonsoft.Json.Linq in C# Manipulates Date String - c#

I've noticed that when parsing JSON using the Newtonsoft.Json.Linq namespace, selecting a date value returns a string in a different format to that of the original JSON. What is causing this?
For example:
JSON
[
{
...
"commit": {
...
"committer": {
"name": "GitHub",
"email": "noreply#github.com",
"date": "2016-12-19T11:53:13Z"
},
...
}
...
}
...
]
C#
...
List<Commit> commits = new List<Commit>();
JArray commitsArray = JArray.Parse(rawCommits);
...
foreach (var entry in commitsArray)
{
DateTime date;
CultureInfo provider = CultureInfo.InvariantCulture;
string format = "MM/dd/yyyy HH:mm:ss";
try
{
date = DateTime.ParseExact((string)entry["commit"]["committer"]["date"], format, provider);
}
catch (FormatException ex)
{
date = new DateTime(0);
}
...
}
...
rawCommits is a string representation of the raw JSON obtained using Microsoft.AspNetCore.WebUtilities.HttpRequestStreamReader().
I would expect (string)entry["commit"]["committer"]["date"] to return the same string as is in the JSON, in this instance in the format "yyyy-MM-ddTHH:mm:ssz" but as the above snippet shows it is instead in the format "MM/dd/yyyy HH:mm:ss". Why has the format changed and what has happened to the time and timezone identifiers?
The only thing I can think is that the call to JArray.Parse(string) identifies and manipulates the date. Is this the case? If so, surely it is undesirable behaviour? If not, what is going on?
Edit
This can be produced with the following example in an .Net Core Console App, adding "Microsoft.AspNetCore.Mvc": "1.1.0" to the project.json file:
using Newtonsoft.Json.Linq;
using System;
namespace JsonExample
{
public class Program
{
public static void Main(string[] args)
{
string json = "{\"date\": \"2016-12-19T11:53:13Z\"}";
JToken jToken = JToken.Parse(json);
Console.WriteLine(jToken["date"]);
Console.ReadLine();
}
}
}
With an output of 19/12/2016 11:53:13, which is interesting because it is yet another format (dd/MM/yyyy HH:mm:ss). Does this perhaps have anything to do with localisation settings? If so, why? It's also confusing, given that IIS Express is running on the same machine as that on which I executed the above code, yet I thought it took the host machines localisation. It also means if I deploy to a server with different localisation than my development machine the format specifier in the original post will because an exception and I'll end up with a value equal to new DateTime(0). What is it I am not understanding?

You are correct, the call to JArray.Parse is parsing the date for you automatically (by design).
I think to get at the raw string you need to use the reader directly:
var s = #"[{
""commit"": {
""committer"": {
""name"": ""GitHub"",
""email"": ""noreply#github.com"",
""date"": ""2016-12-19T11:53:13Z""
}
}
}
]";
using (var sr = new StringReader(s))
using (var jr = new JsonTextReader(sr) { DateParseHandling = DateParseHandling.None })
{
var arr = JArray.ReadFrom(jr);
foreach (var entry in arr)
{
Console.WriteLine(entry["commit"]["committer"]["date"].ToString()); // 2016-12-19T11:53:13Z
}
}

Json.Net stores date-looking values as dates internally, not as strings (it does the same for other base data types as well). So when you start to manipulate these values, you are actually dealing with the internal representation of that value which is a date type, and when you then get it out as a string, you're getting back a string conversion of that internal value, not the original string as you are expecting.
This is a notable difference between dealing with JSON in JS vs. using Json.Net in C# - an unfortunate one IMHO as it requires additional code to work around.

Related

How to convert dynamic property to string

I have some dynamic object, data:
{
...
"start": "2022-09-24T04:04:00Z",
...
}
and I want to get the string value "2022-09-24T04:04:00Z" into a variable. Whenever I try casting, or printing or data.ToString etc, the string is automatically formatted to "24/09/2022 4:04:00 AM".
How can I convert this dynamic property to a string without changing its format?
It looks like it's being interpreted as a datetime, and then calling DateTime.ToString on it, which must convert the format.
The dynamic comes from JsonConvert.DeserializeObject<dynamic> btw.
You can turn off DateTime handling via JsonSerializerSettings:
static void Main(string[] args)
{
var json = "{\"start\": \"2022-09-24T04:04:00Z\"}";
var settings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None };
var result = JsonConvert.DeserializeObject<dynamic>(json, settings);
Console.WriteLine(result["start"]);
Console.ReadLine();
}

How to prevent "Date & Time" conversion while converting a json string to XML doc

I want to convert a JSON response to an equivalent XML document but while doing so, I also want to preserve current date and time format along with the offset e.g., DateTime in JSON is "2019-10-25T07:00:00-05:00" and after conversion I want it to remain the same. But after conversion into XML, the DateTime value becomes "2019-10-25T08:00:00-04:00"
I tried to search about it in Microsoft docs but I didn't find my answer for the following questions:
How to determine the time zone of a given date time string (e.g., "2019-10-25T07:00:00-05:00")?
How can I convert a date time string (e.g., "2019-10-25T08:00:00-04:00") into a date time of the desired time-zone (e.g., into the time zone of "2019-10-25T07:00:00-05:00")
// C# Code Snippet
// Step 1: Reading JsonResponse from a file
string jsonString = System.IO.File.ReadAllText(#"C:\TestDateTimeConvertJSONResponse.txt");
// Step 2: Converting jsonString to XMLDoc
System.Xml.XmlDocument xmlDoc = Newtonsoft.Json.JsonConvert.DeserializeXmlNode(jsonString);
Console.WriteLine();
Console.WriteLine("EQUIVALENT XML RESPONSE");
Console.WriteLine(xmlDoc.InnerXml);
Input JSON string:
{
"Flight": {
"FlightNumber": "747",
"Source": "JFK",
"Destination": "LAS",
"Status": "ON TIME",
"DepDateTime": "2019-10-25T07:00:00-05:00",
"Terminal": "2"
}
}
Expected:
<Flight>
<FlightNumber>747</FlightNumber>
<Source>JFK</Source>
<Destination>LAS</Destination>
<Status>ON TIME</Status>
<DepDateTime>2019-10-25T07:00:00-05:00</DepDateTime>
<Terminal>2</Terminal>
</Flight>
Actual:
<Flight>
<FlightNumber>747</FlightNumber>
<Source>JFK</Source>
<Destination>LAS</Destination>
<Status>ON TIME</Status>
<DepDateTime>2019-10-25T08:00:00-04:00</DepDateTime>
<Terminal>2</Terminal>
</Flight>
Your problem is that Json.NET's automatic DateTime recognition recognizes that the string "2019-10-25T07:00:00-05:00" is a valid ISO 8601 date and time and parses it to a DateTime -- which unfortunately does not have support for timezone specification. Thus the value gets converted (correctly) to the local timezone on your computer during deserialization and subsequently formatted as such in the XML.
To prevent this, you need to parse the JSON using DateParseHandling.None or DateParseHandling.DateTimeOffset, however JsonConvert.DeserializeXmlNode has no overload that allows this setting to be passed in. Thus you will need to create an extension method with the necessary argument:
public static partial class JsonExtensions
{
public static XmlDocument DeserializeXmlNode(string json, DateParseHandling dateParseHandling,
string deserializeRootElementName = null, bool writeArrayAttribute = false, bool encodeSpecialCharacters = false)
{
var settings = new JsonSerializerSettings
{
Converters =
{
new Newtonsoft.Json.Converters.XmlNodeConverter()
{
DeserializeRootElementName = deserializeRootElementName,
WriteArrayAttribute = writeArrayAttribute,
EncodeSpecialCharacters = encodeSpecialCharacters
}
},
DateParseHandling = dateParseHandling,
};
return JsonConvert.DeserializeObject<XmlDocument>(json, settings);
}
}
Then use it as follows:
var xmlDoc = JsonExtensions.DeserializeXmlNode(jsonString, DateParseHandling.None);
Note that both DateParseHandling.None and DateParseHandling.DateTimeOffset meet your needs, as the former disables ISO 8601 date recognition while the latter parses such strings into DateTimeOffset which does support time zone specification.
Incidentally, the equivalent method for those who prefer the newer XDocument is:
public static partial class JsonExtensions
{
public static XDocument DeserializeXNode(string json, DateParseHandling dateParseHandling,
string deserializeRootElementName = null, bool writeArrayAttribute = false, bool encodeSpecialCharacters = false)
{
var settings = new JsonSerializerSettings
{
Converters =
{
new Newtonsoft.Json.Converters.XmlNodeConverter()
{
DeserializeRootElementName = deserializeRootElementName,
WriteArrayAttribute = writeArrayAttribute,
EncodeSpecialCharacters = encodeSpecialCharacters
}
},
DateParseHandling = dateParseHandling,
};
return JsonConvert.DeserializeObject<XDocument>(json, settings);
}
}
Demo fiddle here.
I would read the node as string and write it as a string. That's the only sure way.
Toy example solution JsonConvert.DefaultSetting
// Setting the default settings is the only way I know to affect settings
// for DeserializeXmlNode, there may be a better way
Newtonsoft.Json.JsonConvert.DefaultSettings =
() => new Newtonsoft.Json.JsonSerializerSettings() {
DateParseHandling = Newtonsoft.Json.DateParseHandling.None };
Beware! Changing Newtonsoft.Json.JsonConvert.DefaultSettings can affect other parts of your solution!
Test
var json = #"
{
""Flight"": {
""FlightNumber"": ""747"",
""Source"": ""JFK"",
""Destination"": ""LAS"",
""Status"": ""ON TIME"",
""DepDateTime"": ""2019-10-25T07:00:00-05:00"",
""Terminal"": ""2""
}
}
";
Newtonsoft.Json.JsonConvert.DefaultSettings = () => new Newtonsoft.Json.JsonSerializerSettings() { DateParseHandling = Newtonsoft.Json.DateParseHandling.None };
System.Xml.XmlDocument xmlDoc = Newtonsoft.Json.JsonConvert.DeserializeXmlNode(json);
Console.WriteLine(xmlDoc.InnerXml);
Output
(...)<DepDateTime>2019-10-25T07:00:00-05:00</DepDateTime><Terminal>2</Terminal></Flight>
Output without setting the settings
(...)<DepDateTime>2019-10-25T22:00:00+10:00</DepDateTime><Terminal>2</Terminal></Flight>

How to Parse the first Property even if the Json is not valid?

I am parsing tons of different jsons which only have the first Property in common.
Depending on the value of this first property I parse the json into different object and also handle possible error differently. However it happens that the json is not valid but I still want to know the value of the first property (as long as this is valid) so I can handle the parsing error. I was wondering if this is possible with Json.Net. Of course I assume that at least the first property is valid, something like this for example:
{
"parsingType":"sometype",
"someothervalue":123,
"someval"123,
}
I tried the following but since the exception is thrown when using .Parse I get no result:
JToken jtoken = JToken.Parse(json);
var theValueIWantToGet = jtoken["parsingType"].Value<string>();
I dont think any parsing engine parses json partially. You will have to parse your json string by yourself if parser fails
string json = "{ \"parsingType\":\"sometype\", \"someothervalue\":12}";
var props = json.Replace('{',' ').Replace('}',' ').Split(',').ToList();
if (props.Count > 0)
{
var firstProp = props[0].Split(':');
var propName = firstProp[0];
var propVal = firstProp[1];
}
You can use a JsonReader (probably JsonTextReader as the concrete type) to parse the JSON as a stream, a bit like XmlReader. So for example:
using System;
using System.IO;
using Newtonsoft.Json;
public class Test
{
static void Main(string[] args)
{
using (var reader = new JsonTextReader(File.OpenText("test.json")))
{
while (reader.Read())
{
Console.WriteLine(reader.TokenType);
Console.WriteLine(reader.Value);
}
}
}
}
On the JSON you've provided, that will give output of:
StartObject
PropertyName
parsingType
String
sometype
PropertyName
someothervalue
Integer
123
Unhandled Exception: Newtonsoft.Json.JsonReaderException [...]
So if you always expect there to be a start object, then a property name, then a string property value, you could easily validate that that's the case and extract the property value.

replace a JSON date in a string to a more readable date

We want to show some JSON to a user who is testing our application. So we call our REST service in the ASP.NET code behind file and return a string, which holds a lot of JSON.
We then put it in a PRE element in the page, call beautify to create nice readable JSON and all is good: sort of human readable content is shown.
Good but for one thing: all the dates are shown in the normal JSON format like this "/Date(1319266795390+0800)/"
What I want to do is replace those JSON dates with 'normal' dates, in the JSON (C#) string, so in the code behind that is, before I add the string to the PRE element.
I was thinking about some regex, but i couldn't figure out how...
I'v been dealing with dates in JSON string for some time now, there's no standard way for that and which is why there are so many different ways to do it! Maybe it was better if JSON specification could specify an standard format for dates in the first place!
Microsoft is doing it in its own way, counting the msecs since 1970 in UTC format this is something like "/Date(1319266795390+0800)/"
We've been changing the above string to ISO-8601 format ever since using Regular Expressions on top of ASP.Net JavaScriptSerializer output. It is a W3C standard, human readable and the way most browsers serialize Date to string, here's how:
static readonly long DATE1970_TICKS = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).Ticks;
static readonly Regex DATE_SERIALIZATION_REGEX = new Regex(#"\\/Date\((?<ticks>-?\d+)\)\\/", RegexOptions.Compiled);
static string ISO8601Serialization(string input)
{
return DATE_SERIALIZATION_REGEX.Replace(input, match =>
{
var ticks = long.Parse(match.Groups["ticks"].Value) * 10000;
return new DateTime(ticks + DATE1970_TICKS).ToLocalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff");
});
}
You can easily change the format to satisfy your needs, to see custom Date and Time formats check out MSDN article here
Here's how it's used:
JavaScriptSerializer ser = new JavaScriptSerializer();
var JsonSrt = ISO8601Serialization(ser.Serialize(DateTime.Now)); // "\"2012-05-09T14:51:38.333\""
Update:
There's an alternative to tweak the JSON string returned from the server in JavaScript to more readable form using Regex:
var str = "/Date(1319266795390+0800)/";
str.replace(/\/Date\((\d+)\+\d+\)\//, function (str, date) {
return new Date(Number(date)).toString();
});
The solution is within the string shown in the question. The JavaScript Date object will parse that format and produce a readable version so Date(1319266795390+0800) returns "Wed Apr 18 2012 08:13:22 GMT-0500 (Central Daylight Time)".
To remove the forward slash from the string you could use the replace function with a regular expression: "/Date(1319266795390+0800)/".replace(/\//g, '').
You can use this:
string date = "/Date(1319266795390+0800)/";
string regex = #"/Date\((.*?)\+(.*?)\)/";
Match match = Regex.Match(date, regex);
DateTime d = new DateTime(1970, 01, 01).AddMilliseconds(long.Parse(match.Result("$1")));
suppose the class you want to serialize looks like this:
public class Something
{
public int ID;
public string Name;
public DateTime Date;
}
change it to:
public class Something
{
public int ID;
public string Name;
public DateTime Date;
public string HumanReadableDate { get { return Date.ToLongDateString(); } }
}
or, if you want that extra property to display only in test enviroment:
public class Something
{
public int ID;
public string Name;
public DateTime Date;
#if DEBUG
public string HumanReadableDate { get { return Date.ToLongDateString(); } }
#endif
}
also, instead of .ToLongDateString() you can use .ToString("yyyy-MM-dd HH:mm") or any other format
Use as regex something like:
(?<= /Date\( )
(?<ticks>[0-9]+)
((?<zonesign>[+-])
(?<zonehour>[0-9]{2})
(?<zoneminutes>[0-9]{2})
)?
(?= \)/ )
This will match the part inside the parentheses of /Date(1319266795390+0800)/. You can then call Regex.Replace on the whole JSON string to replace the numbers with a nicely formatted DateTime:
Use the Match object you get in the match evaluator delegate and extract the ticks, zonesign, zonehour and zoneminutes part, convert it to integers.
Then convert the javascript ticks to .NET ticks (should be *10000), construct the .NET DateTime out of ticks and add/substract the hours and minutes for the time zone.
Convert the DateTime to a string and return it as the replacement.
If your JSON is a serialised representation of a .NET class, maybe you could use the DataContractJsonSerializer to deserialise it on the server, or perhaps you could just define a stub class for your JSON object if you don't need a generic solution to handle multiple datasets:
string json = "{\"Test\": \"This is the content\"}";
DataContractJsonSerializer ds = new DataContractJsonSerializer(typeof(TestJson));
var deserialisedContent = ds.ReadObject(new MemoryStream(Encoding.ASCII.GetBytes(json)));
foreach (var field in typeof (TestJson).GetFields())
{
Console.WriteLine("{0}:{1}", field.Name, field.GetValue(deserialisedContent));
}
...
[DataContract]
private class TestJson
{
[DataMember]
public string Test;
}
Use Newtonsoft.JSON. You can provide your own serializers per type, and serialize dates however you want.
http://james.newtonking.com/projects/json-net.aspx
Make a string property for example dateofbirth I am defining here, and return your datetime variable as:
public string DateOfBirthString
{
get { return DateOfBirth.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss"); }
set { DateOfBirth = string.IsNullOrEmpty(value) ? new DateTime(1900, 1, 1) : Convert.ToDateTime(value); }
}
because this will return string so it will be same at client side so and aslo take string dateTime from user and convert it.
string input = [yourjsonstring];
MatchEvaluator me = new MatchEvaluator(MTListServicePage.MatchDate);
string json = Regex.Replace(input, "\\\\/\\Date[(](-?\\d+)[)]\\\\/", me, RegexOptions.None)

Best way to deserialize a long string (response of an external web service)

I am querying a web service that was built by another developer. It returns a result set in a JSON-like format. I get three column values (I already know what the ordinal position of each column means):
[["Boston","142","JJK"],["Miami","111","QLA"],["Sacramento","042","PPT"]]
In reality, this result set can be thousands of records long.
What's the best way to parse this string?
I guess a JSON deserializer would be nice, but what is a good one to use in C#/.NET? I'm pretty sure the System.Runtime.Serialization.Json serializer won't work.
Using the built in libraries for asp.net (System.Runtime.Serialization and System.ServiceModel.Web) you can get what you want pretty easily:
string[][] parsed = null;
var jsonStr = #"[[""Boston"",""142"",""JJK""],[""Miami"",""111"",""QLA""],[""Sacramento"",""042"",""PPT""]]";
using (var ms = new System.IO.MemoryStream(System.Text.Encoding.Default.GetBytes(jsonStr)))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(string[][]));
parsed = serializer.ReadObject(ms) as string[][];
}
A little more complex example (which was my original answer)
First make a dummy class to use for serialization. It just needs one member to hold the result which should be of type string[][].
[DataContract]
public class Result
{
[DataMember(Name="d")]
public string[][] d { get; set; }
}
Then it's as simple as wrapping your result up like so: { "d": /your results/ }. See below for an example:
Result parsed = null;
var jsonStr = #"[[""Boston"",""142"",""JJK""],[""Miami"",""111"",""QLA""],[""Sacramento"",""042"",""PPT""]]";
using (var ms = new MemoryStream(Encoding.Default.GetBytes(string.Format(#"{{ ""d"": {0} }}", jsonStr))))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(Result));
parsed = serializer.ReadObject(ms) as Result;
}
How about this?
It sounds like you have a pretty simple format that you could write a custom parser for, since you don't always want to wait for it to parse and return the entire thing before it uses it.
I would just write a recursive parser that looks for the tokens "[", ",", "\"", and "]" and does the appropriate thing.

Categories

Resources