Changing serialization of Geography in JSON.NET + WebApi2/OData - c#

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 );
}
}
}

Related

Conditionally use custom converter based on depth

I need to apply a custom converter conditionally based on the depth of the reader. The root of the json object is a Def class that should deserialize like normal, however any Defs within the object should be resolved to a reference to that deserialized Def. My plan is to check the depth of the reader, and if we're not at the root, then create a skeleton Def and add it to a list to be resolved later once we've deserialized all the Defs.
public class DefConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(Def);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
if (reader.Depth == 0) {
// Use the default serializer to read the Def
return serializer.Deserialize(reader, objectType); // ?
}
// Create skeleton Def with id
// Add to list of defs to be resolved later
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
The issue I'm running into is that there doesn't seem to be a way to call the Json.NET default converter, using serializer.Deserialize(reader, objectType) will just cause an infinite loop as it just calls the custom converter.
I managed to get it working using the solution here:
Json.NET Recursive Serialisation: Custom converter attribute seems to be being ignored
Toggling the converter off and on using the CanRead getter

How do I deserialize a high-precision decimal value with Json.NET?

I want to deserialize JSON containing long decimal values into custom types to maintain their precision (i.e., a custom BigDecimal class). I'm using Json.NET 9.0.1 and .NET 4.6.1. I've tried using a JsonConverter, but it seems that the value available when ReadJson is called has already been identified and read by Json.NET as a .NET decimal type and is limited to its precision.
Ideally I would have access to the raw string so I could put it in a custom type. I can use string properties on the target object and it deserializes the full string, but then I'd have to further process the object (i.e., copy it into another representation) and that's especially messy across a large schema.
Any thoughts on a better approach?
Here's the target class:
public class DecimalTest
{
public string stringValue { get; set; }
public decimal decimalValue { get; set; }
public BigDecimal bigDecimalValue { get; set; }
}
Here's a test with JSON:
[TestMethod]
public void ReadBigDecimal_Test()
{
var json = #"{
""stringValue"" : 0.0050000012852251529693603515625,
""decimalValue"" : 0.0050000012852251529693603515625,
""bigDecimalValue"" : 0.0050000012852251529693603515625
}";
var settings = new JsonSerializerSettings();
settings.FloatParseHandling = FloatParseHandling.Decimal;
settings.Converters.Add(new JsonBigDecimalConverter());
var result = JsonConvert.DeserializeObject<DecimalTest>(json, settings);
Assert.IsNotNull(result);
Assert.AreEqual("0.0050000012852251529693603515625", result.stringValue);
Assert.AreEqual(0.0050000012852251529693603516m, result.decimalValue);
// *** This case fails ***
Assert.AreEqual("0.0050000012852251529693603515625", result.bigDecimalValue.ToString());
}
Here's the custom converter:
public class JsonBigDecimalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BigDecimal));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// *** reader.Value here already appears to be a .NET decimal.
// *** If I had access to the raw string I could get this to work.
return BigDecimal.Parse(reader.Value.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Could you try if the following implementation of ReadJson works as you expect:
public override object ReadJson(
JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return BigDecimal.Parse(token.ToString());
}
Update
Unfortunately the above won't work. There seems to be no way to read the raw string from the JSON data.
Also note that in my tests the assert for stringValue fails first. See this working example: https://dotnetfiddle.net/s0pqg3
I assume this is because Json.NET internally immediately parses any number token it encounters according to the specified FloatParseHandling. The raw data is never preserved.
I think the only solution is to wrap the big decimal string in quotes like so:
"bigDecimalValue" : "0.0050000012852251529693603515625"
Here is a working example that does exactly that in order to preserve the desired precision: https://dotnetfiddle.net/U1UG3z

Ignore c# fields dynamically from Json Serialize

For API purposes I need to ignore some fields based on criteria I receive. Usually I could use [ScriptIgnore] attribute to do this.
But how I can ignore fields dynamically (based on some conditionals)?
Use JsonIgnore attribute available in Newtonsoft.Json package.
then, if you want it to be dynamically conditional, see ShouldSerialize
Assuming you use Json.Net, you can create your own converter for a specific type by creating a class that inherits from JsonConverter.
public class MyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyType);
}
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 objectToSerialize = new {}; //create the object you want to serialize here, based on your dynamic conditions
new JsonSerializer().Serialize(writer, objectToSerialize); //serialize the object to the current writer
}
}
Then you call JsonConvert.DeserializeObject and pass it your custom converter:
JsonConvert.DeserializeObject<MyType>(jsonString, new MyJsonConverter());

Json.NET does not preserve primitive type information in lists or dictionaries of objects. Is there a workaround?

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

How can I use JSON.NET to handle a value that is sometimes an object and sometimes an array of the object?

I notice there are some other results on stackoverflow for this question but they don't seem to work or are vague. Using the most popular result, I have put together this:
The problem is that when JSON comes back and is being serialised into one of my custom types, one of the bits of JSON is sometimes an array, and sometimes just a string. If my custom type has a string, and the JSON is an array, I get an error. The same happens the other way around, if the JSON is an object and my custom type is an array. It just can't map it to the property.
I decided to solve this, I want to override the deserialisation of this particular property, and if it's an object, I want to convert it into an array of 1 object.
In the object I am serialising to, I added a JsonConverter which I think is going to override the way it's deserialised.
[JsonConverter(typeof(ArrayOrSingleObjectConverter<string>))]
public List<string> person { get; set; }
The idea is that the custom converter will convert a single object to an array. So if the JSON is "Hello" it will set person to be a List containing "Hello" instead of throwing an exception saying cannot convert string to List.
If it's already an array, it should just leave it alone.
The converter looks like this:
public class ArrayOrSingleObjectConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // Not sure about this but technically it can accept an array or an object, so everything is game.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(List<T>))
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
var singleObject = serializer.Deserialize<T>(reader);
var objectAsList = new List<T>();
objectAsList.Add(singleObject);
return objectAsList;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It doesn't work. The above code throws an exception trying to deserialize a a single string saying it can't cast it into a List inside the if statement (the 'objectype' is however a List).
I'm struggling to understand exactly what the read and write methods are doing. In the other answer on stackoverflow, it suggests throwing a NotImplementedException in the read method. But if I do that, the read method is called and the exception throws.
I think I'm on the right track, but I need a nudge in the right direction. I think I'm a little confused about what the ReadJSon method is doing and what its parameters mean.
I don't really understand where the value is coming from that it's deserializing since I didn't specify it in the Deserialize method call.
I'm a bit out of my depth on this one.
I had to do something similar last week and I came up with the following, which works fine for a List rather than an array
internal class GenericListCreationJsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
T t = serializer.Deserialize<T>(reader);
return new List<T>(new[] { t });
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
I like this method which makes Json.NET do all the heavy lifting. And as a result, it supports anything that Json.NET supports (List<>, ArrayList, strongly-typed arrays, etc).
public class FlexibleCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, objectType);
}
var array = new JArray(JToken.ReadFrom(reader));
return array.ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return typeof (IEnumerable).IsAssignableFrom(objectType);
}
}

Categories

Resources