I have a custom JsonConverter which handles the creation of derived types during deserialization, in most cases this works as expected.
The situation where I have an issue is, when there are referenced objects in the json structure.
Is it possible to rely on the default deserialization when we detect a reference? What should the ReadJson method return?
In the sample below we return null in case of a reference.
if (reader.TokenType == JsonToken.Null) return null;
var jObject = JObject.Load(reader);
JToken token;
if (jObject.TryGetValue("$ref", out token))
{
return null;
}
Or must we implement a custom ReferenceResolver as the default one can't be used in the converter (only internal use)?
Any suggestions are welcome.
After some extra testing, I found the solution myself. When I first was trying using the default ReferenceResolver I got an exception saying "The DefaultReferenceResolver can only be used internally.". This pointed my in the wrong direction, you can use the DefaultReferenceResolver in your converter but I was calling it the wrong way...
Solution:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var jObject = JObject.Load(reader);
string id = (string)jObject["$ref"];
if (id != null)
{
return serializer.ReferenceResolver.ResolveReference(serializer, id);
}
// Custom instance creation comes here
}
Related
I'm trying to selectively parse (only certain and not all) valid date strings into DateTimeOffset during deserialization. Has anyone achieved that?
Here's the Json I'm trying to deseralize into a JObject:
{
"SomeDate": "2019-09-19T01:21:00.747Z",
"SomeString": "2019-09-19T01:21:00.747Z"
}
Here's the deserializer I'm using:
JsonSerializer Deserializer = new JsonSerializer
{
DateParseHandling = DateParseHandling.DateTimeOffset
};
As you can expect, here's the JObject I get back:
{
"SomeDate": "2019-09-19T01:21:00.747+00:00",
"SomeString": "2019-09-19T01:21:00.747+00:00"
}
But I'd like to deserialize only SomeDate into a DateTimeOffset and retain SomeString as is:
{
"SomeDate": "2019-09-19T01:21:00.747+00:00",
"SomeString": "2019-09-19T01:21:00.747Z"
}
I can't decorate SomeString property with a custom converter since I'm deserializing to a JObject and therefore I don't have a class to put the decorator
I assume that you want the behavior to be dependent on the serialized value.
If so, you may have to provide your own serializer. See: https://blog.kulman.sk/custom-datetime-deserialization-with-json-net/
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
{
return null;
}
var s = reader.Value.ToString();
if (s.Contains("+"))
{
// DateTimeOffset
}
else
{
// DateTime
}
}
Once you do that, you can register it as a serializer setting.
I have created a simple custom JsonConverter - code below - to simply change the error message generated when an invalid date is passed.
This is being used in an ASP.Net MVC site and also ASP.Net Web API for Ajax calls.
I am posting a DTO (partial code below) via ajax. When i do this on my local environment everything functions as it should; when the date is invalid I receive a bad request along with the correct error message
When i publish this to a production server it's totally ignored and i receive the default ModelState list of errors.
I have verified my web.config and Newtonsoft versions; they're the same both locally and on the server.
Why would the converter be ignored on production?
Converter is:
public class CustomDateValidationConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (string.IsNullOrEmpty(reader.Value.ToString()))
return null;
DateTime d;
DateTime.TryParse(reader.Value.ToString(), out d);
if (d == null || d == DateTime.MinValue)
throw new Exception(string.Format("{0} is invalid", reader.Path.AddSpacesBeforeUppercase(true)));
return d;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, DateTime.Parse(value as string));
}
}
I am using it in my DTO as:
.....
[JsonConverter(typeof(CustomDateValidationConverter))]
[DateLessThanOrEqual("CreateDateTo", "Create Date From must be on or before Create Date To")]
public DateTime? CreateDateFrom { get; set; }
.....
Check Your Production Server Date format, It may differ from your Development environment. so It may accept you input date format as valid
I've written a REST endpoint with ASP.NET Web API 2 that receives dates. The backend system has no concept of UTC times or DST, the dates stored in the database are just the UK date and time.
The website feeding the endpoint, however, is including UTC offset values in the data sent to the end point (e.g. a date looks like "1939-01-08T00:00:00+01:00"). This happens when a summer date is entered in the winter or vice-versa (because of the adjustment for DST).
Is it possible for me to set the JSON deserializer to completely ignore those UTC offset values? I've looked at the docs here and the examples here and tried all of the different enum options, but none of them are giving me the behaviour I'm looking for.
If the date parsing isn't working the way you want, you can turn it off and use a custom JsonConverter to handle the dates instead.
Here is a converter that should do the job:
class OffsetIgnoringDateConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(DateTime) || objectType == typeof(DateTime?));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string rawDate = (string)reader.Value;
if (rawDate == null) return existingValue;
if (rawDate.Length > 19) rawDate = rawDate.Substring(0, 19);
DateTime date = DateTime.ParseExact(rawDate, "yyyy'-'MM'-'dd'T'HH':'mm':'ss",
CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal);
return date;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To install this converter into the Web API pipeline, add the following to your Application_Start() method in Global.asax:
var config = GlobalConfiguration.Configuration;
var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.DateParseHandling = DateParseHandling.None;
jsonSettings.Converters.Add(new OffsetIgnoringDateConverter());
Here is a demo (console app): https://dotnetfiddle.net/kt9pFl
Given the following class:
[DataContract]
public class Enumerables
{
[DataMember]
public IEnumerable<Byte> ByteMember { get; set; }
}
And an instance initialized as:
var bytes = new byte[] { ... };
var o = new Enumerables { ByteMember = bytes };
Serialization produces this:
{"ByteMember": "<<base-64-encoded-string>>"}
But this string cannot be deserialized. The error produced is:
Newtonsoft.Json.JsonSerializationException : Error converting value
"vbMBTToz9gyZj6gZuA59rE7ryu3fCfimjVMn8R6A0277Xs9u" to
type 'System.Collections.Generic.IEnumerable`1[System.Byte]'.
Path 'ByteMember', line 1, position 8084.
----> System.ArgumentException : Could not cast or convert from
System.String to System.Collections.Generic.IEnumerable`1[System.Byte].
I don't see this happening for byte[], List<byte> or Collection<byte> properties, which are correctly serialized to and from base-64 strings. And I don't see this happening for any IEnumerable<T> where T is not a byte -- for example, a property of type IEnumerable<int> deserializes to a List<double>, an effective implementation.
How an IEnumerable<byte> gets serialized depends on the concrete type that is assigned to it. If the concrete type is a byte[] then it will get serialized specially as a base-64 encoded string, whereas if it is some other concrete type like a List<byte>, it will be serialized as a normal array of numbers. The same is true of ICollection<byte> and IList<byte>. (DEMO)
On deserialization, Json.Net looks at the types of the member properties of the target class to determine what types of objects to create. When the member property is a concrete type, no problem; it creates an instance of that type and tries to populate it from the JSON. If the member type is an interface, then Json.Net has to make a guess, or throw an error. You could argue that Json.Net should be smart enough to guess that if the member variable is an IEnumerable<byte> and the JSON value is a base-64 encoded string, it should convert the string to a byte[]. But that is not how it is implemented. In fact, the special handling for base-64 encoded byte arrays is only triggered if the member property is byte[] specifically. With no special handling for IEnumerable<byte>, this results in an error because a string can't be assigned directly to an IEnumerable<byte>. Again, the same is true for ICollection<byte> or IList<byte>.
(DEMO)
If you want it to work the same for types implementing IEnumerable<byte> as it does for byte[], you can make a custom JsonConveter like this:
public class EnumerableByteConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IEnumerable<byte>).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
byte[] bytes = ((IEnumerable<byte>)value).ToArray();
writer.WriteValue(Convert.ToBase64String(bytes));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
byte[] bytes = Convert.FromBase64String(reader.Value.ToString());
if (objectType == typeof(byte[]))
{
return bytes;
}
return new List<byte>(bytes);
}
}
To use the converter, create an instance of JsonSerializerSettings and add an instance of the converter to the Converters collection. Then, pass the settings to SerializerObject() and DeserializeObject() methods. For example:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new EnumerableByteConverter());
string json = JsonConvert.SerializeObject(obj, settings);
Here is a working round-trip demo. Note, however, that this converter doesn't (and can't) handle every possible IEnumerable<byte> that might be out there. For example, it won't work with ISet<byte> as currently implemented. If you need support for this or other additional types, you will need to extend the ReadJson method to handle that. I leave this to you.
I'm using Entity Framework Code First table-per-hierarchy in my ASP.NET Web API project. One of my models has a List that is of the type of the abstract base class of the hierarchy.
List<Dog> // (for example; with GoldenRetriever, BorderCollie, etc inheriting)
I'm trying to test POSTing some data to my API Controller using Fiddler. But I don't know how to represent the JSON when I do so. If I try something like:
"Dogs":
[
{"Name":"Bud", "Age":"3"}
]
I get the error:
"Could not create an instance of type Models.Dog. Type is an interface
or abstract class and cannot be instantiated."
Specifying the Discriminator in the JSON doesn't help me either. Anyone have any ideas? Thanks!
Edit: Solution
The trick is to use the $type property in the JSON string. For more information see this link suggested by m.t.bennett in the comments.
To enable using the $type property I needed to add the following to WebApiConfig.cs:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling
= TypeNameHandling.Auto;
Then when posting the JSON to Fiddler, I added the $type property with the full object path:
{"$type":"Example.Models.Dogs.GoldenRetriever, Example.Models",
"Name":"Bud","Age":3}
For me to figure out this formatting I used Snixtor's suggestion to serialize an object and output the JSON string. Brilliant!
I'm not sure if this is the most elegant solution since it's JSON.NET specific, but it works!
I used a custom JsonConverter to handle the base-type deserialization.
public override bool CanConvert(Type objectType) {
return typeof(Mybase).Equals(objectType);
}
public override MyBase Deserialize(JsonReader reader, MyBase existingValue, JsonSerializer serializer) {
var ret = existingValue;
var jobj = JObject.ReadFrom(reader);
if (jobj["type"] == null || jobj["type"].Type != JTokenType.String)
throw new JsonSerializationException("Supplied JSON is missing the required 'type' member.");
var typeName = jobj["type"].Value<string>();
var t = this.GetType().Assembly.GetType("MyNamespace." + typeName);
if(t == null)
throw new JsonSerializationException(String.Format("Could not identify supplied type '{0}' as a known type.", typeName));
if (existingValue != null && !t.IsInstanceOfType(existingValue))
throw new JsonSerializationException(String.Format("Type Mismatch: existingValue {0} is not assignable from supplied Type {1}", existingValue.GetType().Name, t.Name));
ret = (ContactMethod)jobj.ToObject(t);
return ret;
}
And registered the JsonConverter durring application initialization:
JsonSerializerSettings settings;
settings.Converters.Add(new MyBaseConverter());