Conditionally use custom converter based on depth - c#

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

Related

Custom JsonConverter does not call ReadJson

I am trying to implement a custom JsonConverter for a struct, but I'm having a hard time getting it to work. I have previously implemented a custom converter for another class and that one works flawlessly, I thought I did the same thing for this one but the ReadJson method of my converter is never called.
This is the class:
public class TransformMatrixConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TransformMatrix).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And below is how I use it in my Item class. You can see my other converter, which works fine.
public static Item FromJSON(string json)
{
JsonConverter[] converters = { new LineConverter(), new TransformMatrixConverter() };
return JsonConvert.DeserializeObject<Item>(json, new JsonSerializerSettings()
{
Converters = converters
});
}
What happens: the CanConvert method of my converter is called and correctly returns true when appropriate; however, the ReadJson method is never hit, I have a breakpoint there and also that exception is never thrown. I have verified that the CanRead property of the converter is true. I am at a loss here, any ideas?
I think I solved it with the help of Brian, the problem was unrelated to the code above - the TransformMatrix I thought I was deserializing was just a read-only property. The solution I used was to expose another property which the deserializer can write.

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

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

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

Why JsonConverter.WriteJson() never gets called, although JsonConverter.ReadJson() does get called?

Why my custom JsonConverter.WriteJson() method doesn't get called ?
class MyType{
[JsonConverter(typeof(DocumentXamlDeserializer))]
public string GuiData { get; set; }
public string SimpleString;
}
Although the ReadJson does get called:
public class DocumentXamlDeserializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(Gui.Handler.SerializeData());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var xaml = reader.Value as string;
Gui.Handler.DeserializeData(xaml);
return xaml;
}
public override bool CanConvert(Type objectType) { return typeof(string).IsAssignableFrom(objectType); }
}
The serialization call: JsonConvert.SerializeObject(dataModel, Formatting.Indented);
The deserialization call: JsonConvert.DeserializeObject<Model>(raw);
Apparently this is because GuiData is null... I guess I could specify:
TypeNameHandling = TypeNameHandling.Objects
But I want to serialize only GuiData even if it's null (I set its value during serialization), without serializing all null-properties... well, if I don't find better way, I guess I'd have to suffice with it...

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