Json.Net specify custom converter of a property of a generic class - c#

To specify which converter Json.Net serializer should use when dealing with a property, we usually use JsonConverterAttribute, which is a nice and neat syntax.
However, because C# doesn't support the use of generic type parameters in Attribute, trouble occurs when the declaring class of property contains generic type parameters. Here's a simple example:
public class FooConverter<T> : JsonConverter<T> {
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) {
//Implementation
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) {
//Implementation
}
}
public class Bar<T>{
[JsonConverter(typeof(FooConverter<T>))] //CS0416 'T': an attribute argument cannot use type parameters
public T Value { get; set; }
}
I know I can instantiate a generic converter and pass it with JsonSerializerSettings when calling JsonConvert.DeserializeObject, but that requires the caller to know such implementation detail of the type being deserialized. What I'm writing is a library, I don't want to bother my user to manually add a converter each time he needs to deserialize a object of such type.
Is there any workaround when my custom converter do rely on the type parameter?

You'll need to use a non-generic converter, e.g.
public class FooConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// ...
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// ...
}
public override bool CanRead
{
get { return true; } // or false if you don't need custom read
}
public override bool CanConvert(Type objectType)
{
// ...
}
}
Then you can use [FooConverter] as an attribute. It won't be type-safe but there's no other way to do this because of the unbound nature of generics. However, as long as you return an object of type objectType in the ReadJson method, you won't have any problems.
More information is here.

Related

Conditionally applying JsonConverter

I have a data structure we use to make api update calls to TFS and we set the Value property as a generic which works well. We'd like to use enums in our client code as representations of some types but they serialise as integers. So there is the [JsonConvert(typeof(StringEnumConverter))] option, on a generic this is an issue as it tries to serialise all values as string against the enum type of the property. It looks like this:
public class WorkItemUpdateData<T> : WorkItemUpdate
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("value")]
public T Value { get; set; }
}
Is it possible to conditionally apply the converter when the property T is enum only?
By implementing a custom converter and specifying this as the converter:
public class OnlyEnumStringConverter : StringEnumConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null && value.GetType().IsEnum)
{
base.WriteJson(writer, value, serializer);
}
else
{
serializer.Serialize(writer, value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType.IsEnum)
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}
else
{
return serializer.Deserialize(reader, objectType);
}
}
}
Specifying it:
public class WorkItemUpdateData<T> : WorkItemUpdate
{
[JsonConverter(typeof(OnlyEnumStringConverter))]
[JsonProperty("value")]
public T Value { get; set; }
}
Might it be easier to "sideclass" this, have a WorkItemUpdateData and a WorkItemUpdateEnumData ? Then your conditional logic to have the JsonConverter apply can be made when creating the object rather than when serializing it.
public class WorkItemUpdateData<T> : WorkItemUpdate
{
[JsonProperty("value")]
public T Value { get; set; }
}
public class WorkItemUpdateEnumData<T> : WorkItemUpdate
{
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty("value")]
public T Value { get; set; }
}
You could also include something in the setters to ensure they're used correctly, like
if (!typeof(T).IsEnum) throw ...

Serialize from and deserialize into different object property from same JSON property

I'd like to serialize a property from a class into a json property, and when deserializing back, i'd like to read it to a different property.
Cannot figure out how to achieve this in json.net
Minimal example:
Backend converts a specially formatted json property into a numeric timestamp (firebase systemtimestamp).
I want to use the same abstract base class for both serialization and deserialization.
When serializing, i need to generate a "timestamp" json property with special values.
When deserializing, incoming json contains a number in "timestamp" property. I obviously need this to be read into a different class property.
Class:
[JsonObject(MemberSerialization.OptIn)]
public abstract class ItemBase
{
[JsonProperty("timestamp")]
private TimestampModel WriteTimestamp => TimestampModelInstance;
[JsonProperty("timestamp")]
private long ReadTimestamp { get; set; }
public DateTime Timestamp => DateTimeOffset.FromUnixTimeMilliseconds(ReadTimestamp).LocalDateTime;
}
I would need to somehow mark WriteTimestamp as "serialize only", and ReadTimestampas "deserialize only"
If anyone finds this useful, I resolved the issue with a custom JsonConverter.
This way I have only one class property that has the type of the read value (long), that always serializes into TimestampModelInstance (the special firebase structure) regardless of the actual value
Converter:
public class TimestampConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TimestampModel);
}
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)
{
JObject.FromObject(TimestampModelInstance).WriteTo(writer);
}
}
In Class:
[JsonProperty("timestamp")]
[JsonConverter(typeof(TimestampConverter))]
private long NumericTimestamp { get; set; }
public DateTime Timestamp =>
DateTimeOffset.FromUnixTimeMilliseconds(NumericTimestamp).LocalDateTime;

JsonConverter for Custom Deserialization

I have implemented a custom JsonConverter to deserialize concrete classes when all we know about is the interface that is using them. given this, I have overridden the ReadJson method with the following:
public class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IMyInterface));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
if (jsonObject["Type"].Value<string>() == "MyType")
return jsonObject.ToObject<MyConcrete>(serializer);
}
}
On the interface I have added the following decoration:
[JsonConverter(typeof(MyConverter))]
public interface IMyInterface { ... }
The issue I seem to be having is that when deserialization is attempted, I keep getting StackOverflow exception, and I think that it is going around this custom converter for each element in the JSON structure.. which there are quite a few(!)
If I remove the decoration of the interface and instead add the custom converter to the call the converter manually... it works fine!
var jsonSerializerSettings = new JsonSerializerSettings
{
Converters = { new MyConverter() }
};
Is there a way I can resolve this without calling the customer converter specifically? I am concerned about unexpected behavior on deserialization of objects that don't need the custom JsonConverter. Thoughts?

JSON serialize a custom C# struct to a string, not an Object

I've looked at various questions but I am unsure of how to implement this.
I have a custom struct, which currently has no public properties on it. When it is returned via WebApi (not doing any fancy serialization, just returning the custom struct itself), it is returned as an object {}.
public struct CustomStruct
{
private string myProperty;
...
public override string ToString()
{
return this.myProperty;
}
...
}
The custom struct itself is the type of a property on a parent class, which serializes to:
{ "MyProp1":"value1","MyProp2":"value2","MyCustomStruct":{} }
When I override ToString() on the custom struct I want to output one of the private properties. Can I achieve a similar behaviour when returning the object to JavaScript-land, as a JSON object?
E.g. my private property is a string, called "myProperty", set to "test".
If I added a public property called "MyProperty", I'd get the following output:
{ "MyProp1":"value1","MyProp2":"value2","MyCustomStruct":{ "MyProperty":"test" } }
When what I really want is:
{ "MyProp1":"value1","MyProp2":"value2","MyCustomStruct":"test" }
Hope this makes sense.
Here are the related questions that haven't really helped me much. Would like to avoid using JSON.NET if possible but will go for that if it is the only way:
JSON.Net Struct Serialization Discrepancy
C# custom json serialization
JSON.NET with Custom Serializer to a custom object
JSON serialization of enum as string
JavaScriptSerializer.Deserialize - how to change field names
I faced the same challenge. I solved it by writing a custom JsonConverter that uses the objects ToString method when converting:
public class ToStringConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
And then using it like this as JsonConverter attribute:
[JsonConverter(typeof(ToStringConverter))]
public AnotherObjectWithToString MyObject { get; set; }
I should note that this can only be used for serialization as deserialization would require the ToString result to converted back into a object and that will vary by type.
What I have done for now, is add a second property on the parent CustomClass class...
public string MyCustomStructValue { get { return MyCustomStruct.ToString(); } }
then add the [IgnoreDataMember] attribute to the original property...
[IgnoreDataMember]
public CustomStruct MyCustomStruct { get; set; }
which works fine with the following action:
public IEnumerable<CustomClass> Get()
{
return GetResults();
}

Deserializing IList objects with Newtonsoft JSON

I'm using a ConcreteCollectionTypeConverter to deserialize objects of type IList. I'm probably doing it very wrong because I keep getting the exception "Cannot convert List to IList"
My object inteface looks something like this:
[JsonObject(MemberSerialization.OptIn)]
public IMyInterface {
[JsonProperty("associatedContact")]
[JsonConverter(typeof(ConcreteCollectionTypeConverter<IList<ISomeOtherInterface>, List<SomeOtherInterface>>))]
IList<ISomeOtherInterface> MyObject { get; set; }
}
My object implementation looks like this:
public MyImplementation : IMyInterface {
public List<SomeOtherImplementation> MyObject { get; set; }
public MyImplementations () {
MyObject = new List<SomeOtherImplementation>();
}
}
My Json Converter looks like this:
class ConcreteCollectionTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
return serializer.Deserialize<TImplementation>(reader);
}
}
I'm calling the deserialization method somewhere else in my project like this:
var myDeserializedObject = JsonConvert.DeserializeObject<MyImplementation>(jsonObject.ToString());
My jsonObject mentioned above is correctly parsed.
I know this is possible because I've seen a comment here on SO from a user caliming he's done it (Casting interfaces for deserialization in JSON.NET - first comment of the accepted answer).
This approach works fine for non-collection items (i.e. deserializing an object of an interface type IIterface MyObject with JsonConverer(typeof(ConcreteTypeCOnverter)) works).
If I use only Lists instead of ILists, I don't even need the Type Converter, but my requirements are to assure usage of ICollections because of possible non standard implementations.
I believe this will work for what you need:
public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var res = serializer.Deserialize<List<TImplementation>>(reader);
return res.ConvertAll(x => (TInterface) x);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}

Categories

Resources