Background
I need to override the below method so it will deserialize an object's property without failing.
JsonConvert.DeserializeObject()
It is failing because it is trying to convert a key pair value which contains either "Y" or "N" to a property which is of type Boolean.
This is the error
Could not convert string to boolean: Y.
I'm calling the method like this:
private List<T> GetBindingSource<T>(List<T> list, string JsonKey, Dictionary<string, string> dictOriginalJSON)
{
var OutJson = "";
if (dictOriginalJSON.TryGetValue(JsonKey, out OutJson))
{
list = JsonConvert.DeserializeObject<List<T>>(OutJson); //Call fails here
}
return list;
}
My Attempted Solution
After reading up on the issue it seems the best thing to do would be to override the method. I've chosen this solution by #entre
How to get newtonsoft to deserialize yes and no to boolean
using System;
using Newtonsoft.Json;
namespace JsonConverters
{
public class BooleanJsonConverter : JsonConverter
{
public override bool CanConvert( Type objectType )
{
return objectType == typeof( bool );
}
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
switch ( reader.Value.ToString().ToLower().Trim() )
{
case "true":
case "yes":
case "y":
case "1":
return true;
case "false":
case "no":
case "n":
case "0":
return false;
}
// If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.
return new JsonSerializer().Deserialize( reader, objectType );
}
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
}
Problem
When I make the new call to the extended class like below.
list = BooleanJsonConverter.DeserializeObject<List<T>>(OutJson);
I get this error message.
QueueDetail.BooleanJsonConverter' does not contain a definition for
'DeserializeObject'
Question
What am I doing wrong? This is the first time I've attempted this, so I may well be missing something.
If BooleanJsonConverter inherits JsonConverter. Why doesn't BooleanJsonConverter contain the call i was using previously ?
You haven't told JsonSerializer to use your converter.
See documentation of JSON.NET here:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
then You can try call like this:
JsonConvert.DeserializeObject<<List<T>>(OutJson, new BooleanJsonConverter(typeof(<List<T>)));
You can also use Json atributte in Your T object.
[JsonConverter(typeof(BooleanJsonConverter))]
Related
This question already has answers here:
Best way to upgrade JSON field to a class structure
(1 answer)
Json.NET custom serialization with JsonConverter - how to get the "default" behavior
(1 answer)
Closed 1 year ago.
I'm trying to make a "reference" system, where JSON files can depend and reference other files. I know JSON.net has a built-in system like this but it doesn't fit my use case.
I implemented it like this:
public class ReferenceJsonConverter : JsonConverter<NamedType>
{
private readonly ContentLoader _contentLoader;
public ReferenceJsonConverter(ContentLoader contentLoader)
{
_contentLoader = contentLoader;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, NamedType value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override NamedType ReadJson(JsonReader reader, Type objectType, NamedType existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
{
string value = reader.Value!.ToString().ToLower();
return _contentLoader.Load<NamedType>(value);
}
default:
{
NamedType namedType = (NamedType?) serializer.Deserialize(reader, objectType);
return namedType;
}
}
}
}
So pretty simple, if the token is a string it gets it from memory instead of deserializing it. The problem is, since the token type is the same as the parent type, this will loop infinitely because it will keep trying to use ReferenceJsonConverter to deserialize non-strings.
I've tried the following:
Re-implement CanConvert to try and "skip" StartObject tokens (janky, didn't work)
Have a different JsonConverter do the StartObject, then deserialize using ReferenceJsonConverter on string tokens (loops infinitely because I can't "skip" the converter)
Re-implement the JObject convert method (wayy too much work, will break on updates, and it's all internal anyways)
For clarity, it would look like this:
{ <--- Serialized NamedType, should use default deserializer
"Name": "Test", <-- Field of NamedType, should use default deserializer
"ReferenceOther": "ReferencedType" <--- Referenced NamedType, should use ReferenceJsonConverter
}
I'm having some difficulties in deserializing a Boolean from a json. I need that the value can be case insensitive (faLSe, tRUE, etc) and if it's an invalid value (ex: qwerty) I'll return a null. I've created a custom converter:
public class NullableBooleanJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string value = reader?.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (bool.TryParse(value, out bool deserializedValue))
{
return deserializedValue;
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}
And I'm using the code like this:
JsonSerializerSettings setting = new JsonSerializerSettings
{ Converters = new List<JsonConverter> { this.converter } };
JsonConvert.DeserializeObject<bool?>("false", setting).Should().BeFalse();
and this works, but if I have on the last line:
JsonConvert.DeserializeObject<bool?>("faLSE", setting).Should().BeFalse();
The test fails with the message:
TestName threw exception:
Newtonsoft.Json.JsonReaderException: Error parsing boolean value. Path '', line 1, position 2
What I'm doing wrong?
If you look at the source code of DeserializeObject it uses the JsonTextReader class to parse the Json. If you look at the source code of JsonTextReader it parses only "true" (if first char is a lowercase t) or "false" (if first char a lowercase f) strings (row 1720), throwing an exception in any other case.
The simplest way to realize what you need is to download the full Newtonsoft.Json source code, add a String.ToLower to ParseTrue and ParseFalse methods in JsonTextReader.cs, recompile and use the new dll in your project.
I have a generic type Container<IContentType> where interface IContentType can be one of four concrete ContentX types.
I serialize and everything is fine.
When deserializing using Newtonsoft I use custom type converters and var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings) works. The debugger shows I have an Container<ContentA> object.
My plan was when deserializing to attempt a deserialization for each of the four possible ContentX types and catch an exception silently until I "guess" the right one.
However, if I do this within a method like so:
public static Container<IContentType> Deserialize(jsonfile)
{
...
var model = JsonConvert.DeserializeObject<Container<ContentA>>(json, settings)
return model;
}
I get "Cannot implicitly convert Container<ContentA> to Container<IContentType>". ContentA implements IContentType.
Is there a way I can create a cast operator, conversion, dynamic or make the implicit conversion work?
Rather than trying to deserialize as a Container<ContentX> for concrete type(s) X, you should deserialize as a Container<IContentType> using a custom JsonConverter that pre-load the JSON into a JToken and infers the concrete type along the lines of How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects? or Deserializing polymorphic json classes without type information using json.net or Json.Net Serialization of Type with Polymorphic Child Object.
Thus your converter would look something like:
public class ContentConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IContentType);
}
Type GetConcreteType(JObject obj)
{
if (obj.GetValue(nameof(ContentA.SomePropertyOfContentA), StringComparison.OrdinalIgnoreCase) != null)
return typeof(ContentA);
// Add other tests for other content types.
// Return a default type or throw an exception if a unique type cannot be found.
throw new JsonSerializationException("Cannot determine concrete type for IContentType");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var concreteType = GetConcreteType(obj);
return obj.ToObject(concreteType, serializer);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And your call to JsonConvert would look like:
var settings = new JsonSerializerSettings
{
Converters = { new ContentConverter() },
};
var model = JsonConvert.DeserializeObject<Container<IContentType>>(json, settings);
Finally, you might be able to choose the type entirely automatically using
new JsonDerivedTypeConverer<IContentType>(typeof(ContentA), typeof(ContentB), typeof(ContentC), typeof(ContentD))
Where JsonDerivedTypeConverer<T> is taken from JsonConverter with Interface.
The following example illustrates a fundamental flaw in Json.NET's type handling:
List<object> items = new List<object>() {Guid.NewGuid(),DateTime.Now};
var settings = new JsonSerializerSettings() { TypeNameHandling=TypeNameHandling.All };
var json = JsonConvert.SerializeObject<List<object>>(value,settings);
resulting in the following JSON:
{"$type":"System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib","$values":["9d7aa4d3-a340-4cee-baa8-6af0582b8acd","2014-07-28T21:03:17.1287029-04:00"]}
As you can see the list items have lost their type information. Deserializing that same JSON will result in a list containing just strings.
This issue was previously reported on codeplex and perfunctorily closed, stating including the type information would make the JSON too messy. I am surprised we aren't given a separate option to include primitive type information for such scenarios as the round-trip consistency is broken.
https://json.codeplex.com/workitem/23833
I would expect the data to come back with the same type information that it left with.
Does anybody have any suggestions or workarounds to remedy this undesired behavior?
Thanks,
Chris
Here is a solution using a custom JsonConverter:
public sealed class PrimitiveJsonConverter : JsonConverter
{
public PrimitiveJsonConverter()
{
}
public override bool CanRead
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return objectType.IsPrimitive;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
switch (serializer.TypeNameHandling)
{
case TypeNameHandling.All:
writer.WriteStartObject();
writer.WritePropertyName("$type", false);
switch (serializer.TypeNameAssemblyFormat)
{
case FormatterAssemblyStyle.Full:
writer.WriteValue(value.GetType().AssemblyQualifiedName);
break;
default:
writer.WriteValue(value.GetType().FullName);
break;
}
writer.WritePropertyName("$value", false);
writer.WriteValue(value);
writer.WriteEndObject();
break;
default:
writer.WriteValue(value);
break;
}
}
}
Here is how to use it:
JsonSerializerSettings settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
};
settings.Converters.Insert(0, new PrimitiveJsonConverter());
return JsonConvert.SerializeObject(myDotNetObject, settings);
I'm currently using this solution to serialize an IDictionary<string, object> instance that can contain primitives.
Hacked this together and tested it out. Obviously this needs unit testing and is more a proof of concept. If you want a dirty solution to get you going this should get one started.
https://github.com/xstos/Newtonsoft.Json/commit/8d3507cbba78f7096a82e42973e56d69c9541c42
I have a simple ADO.NET Entity Model which I'm exposing using OData. One of the fields in the entity model is a Geography type (geography in SQL Server). I can query the data just fine, and I get the following serialized format for the geography columns:
"Shape":{
"WellKnownValue":{
"CoordinateSystemId":4326,
"WellKnownText":"POLYGON ((...)",
"WellKnownBinary":null
}
So this works, but I'm hoping I can change the serialization of this object to make it more like:
"Shape":"4326:POLYGON((...))"
Admittedly this is mostly for aesthetics, but it'd be nicer to have a simpler graph and shorter message too.
I wrote the following class which I thought would help:
public class JsonGeographyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.Equals(typeof(DbGeography));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var geog = (DbGeography)value;
if (geog != null)
writer.WriteValue(string.Format("{0}:{1}", geog.WellKnownValue.CoordinateSystemId, geog.WellKnownValue.WellKnownText));
else
writer.WriteNull();
}
}
And added it to the JSON serializer settings in my OData configuration:
var config = new HttpConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonGeographyConverter());
But it doesn't seem to make a difference. In fact, a breakpoint placed in CanConvert is never reached, so I'm inclined to think that I'm not setting up JSON correctly.
I also tried:
var config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonGeographyConverter());
but this also had no effect.
Hopefully someone can point out what I'm doing wrong?
Although Web API iteself uses the Json.Net serializer, a little digging around in the source code seems to indicate that the MediaTypeFormatter for Web API OData uses its own internal serializer which is not Json.Net. Therefore, adding a Json.Net converter to the configuration will not have any effect on OData. Unfortunately, without a deep-dive analysis of the code, I do not know whether OData's serializer is extensible in the same way, and/or whether it is possible to get it to use Json.Net instead.
I required some asthetics as well since I did not want to read into a json object when all I required was the latlng on the client side so I did the same.
My code is below. Been working for awhile now with no issues.
public class DbGeographyConverter : JsonConverter
{
public override bool CanConvert ( Type objectType )
{
return objectType.IsAssignableFrom( typeof( DbGeography ) );
}
public override object ReadJson ( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
if ( reader.Value == null ) {
return null;
}
return Parser.ToDbGeography( reader.Value.ToString() );
}
public override bool CanWrite { get { return true; } }
public override void WriteJson ( JsonWriter writer, object value, JsonSerializer serializer )
{
//Attempting to serialize null dosent go well
if ( value != null ) {
var location = value as DbGeography;
serializer.Serialize( writer, location.Latitude + "," + location.Longitude );
}
}
}