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;
Related
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 ...
I have the following two example classes. Firstly the User class...
public class User : ILoadable
{
public User(string code)
{
this.Load(code);
}
public string Code { get; set; }
public UserGroup Group { get; set; }
}
... and the UserGroup class.
public class UserGroup : ILoadable
{
public UserGroup(string code)
{
this.Load(code);
}
public string Code { get; set; }
public int GroupId { get; set; }
// More properties
}
Then I have a method filling the object with data from a json file which is called from the constructors:
public static void Load(this ILoadable obj, string code)
{
string json = GetJsonFromCode(obj.GetType(), code);
JsonConvert.PopulateObject(json, obj);
}
What I want is not to save the User with its complete UserGroup property data, but only with its code, so it can be reconstructed by passing the code to the UserGroup constructor and getting the whole object from there. For example like this:
{
"UserCode": "Admin",
"Group": "Administrator"
}
I already tried creating a JsonConverter and setting it for the Group property with the following code...
[JsonProperty(ItemConverterType = typeof(StringObjectConverter)]
public UserGroup Group { get; set; }
... and converter:
class StringObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(ILoadable).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return Activator.CreateInstance(objectType, new object[] { (string)reader.Value });
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((ILoadable)value).Code);
}
}
But it does not seem to work, because everytime I try to load above seen json, the following exception is thrown:
Newtonsoft.Json.JsonSerializationException: 'Error converting value "Administrator" to type 'MyProject.UserGroup'. Path 'Group', line 2, position 10.'
Inner Exception
ArgumentException: Could not cast or convert from System.String to MyProject.UserGroup.
I could use a little help here, cause I don't know how get this to work, when even the converter does not change anything.
Rather than [JsonProperty(ItemConverterType = typeof(StringObjectConverter))] you must use [JsonConverter(typeof(StringObjectConverter))]:
public class User : ILoadable
{
public User(string code)
{
this.Load(code);
}
public string Code { get; set; }
[JsonConverter(typeof(StringObjectConverter))]
public UserGroup Group { get; set; }
}
Notes:
JsonConverter Instructs the JsonSerializer to use the specified JsonConverter when serializing the member or class.
You want to specify a converter to use for the UserGroup Group member, so this is the attribute to apply.
JsonPropertyAttribute.ItemConverterType specifies a JsonConverter used when serializing the property's collection items.
You would want to apply this attribute if you have a collection, say a List<UserGroup> Groups, and need to specify a converter for each item. Since you don't have a collection, this is not the correct attribute to use.
(Apparently when an ItemConverterType is applied to a member whose value is serialized as a JSON object, Json.NET applies the converter to every member of that object. This is a bit surprising; I can't find any place this is explicitly documented. This accounts for the exception you are seeing.)
Working .Net fiddle.
I want to decorate my classes with custom attributes, and read them when I convert to json using json.net inside a custom JsonConverter. I'll then vary the serialization depending on this custom attribute.
public class MyCustomJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//I want to get any attributes set on the property here.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Int64);
}
}
Another approach would be to specify my custom JsonConverter on the property using an attribute, but I don't want to do this because I want to inject some behaviour into the constructor of my custom JsonConverter by instantiating the converters in the JsonSerializer settings as below.
String json = JsonConvert.SerializeObject(new MyCLass(), new JsonSerializerSettings
{
Converters = new List
{
new MyCustomJsonConverter()
}
});
I can get to the name of the property in the textWriter path. And I can see some interesting hints in the documentation about Metadata, but I can't find a way to do this.
Here's an example decorated class:
public class MyCustomAttribute : Attribute { }
public class MyCLass
{
[MyCustom]
public Int64 MyInt { get; set; }
}
JsonConverters apply to types, not to fields or properties.
Instead of adding an attribute to a property that uses an existing type, consider creating a new type and writing a convert for that instead.
public struct MyCustomType
{
...
}
public class MyClass
{
public MyCustomType {get; set;}
}
Besides - in what other way would you ever want to serialize a raw integer? If the integer represents something, then create a struct or class for that something.
See also: "ValueObject" (Domain Driven Design fundamental concept)
Based on your comment below, an alternate approach would be to forget about JsonConverters and simply expose a secondary property:
public class MyClass
{
[JsonIgnore]
public Int64 MyInt {get; set;}
[JsonProperty("MyInt")]
public Int64 MyEncryptedInt
{
get { return Encrypt(MyInt); }
set { MyInt = Decrypt(value); }
}
}
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();
}
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);
}
}