Serializing Generic KeyValue item issue in Newtonsoft - c#

I have this class structure:
public class GraphDataItem<TCategory, TValue>
{
public TCategory Category { get; set; }
public TValue Value { get; set; }
public GraphDataItem(TCategory category, TValue value)
{
Category = category;
Value = value;
}
}
I have a collection of this items, that I serialize using something like this:
List<GraphDataItem<DateTime, int>> items = GetItems();
var json = JsonConvert.SerializeObject(items);
The json output that I get looks like this:
[{"Category":"2014-04-30T00:00:00","Value":1},
{"Category":"2014-05-01T00:00:00","Value":38},
{"Category":"2014-05-02T00:00:00","Value":18}]
I want the default DateTime serializing behaviour, that will produce something like:
[{"Category":/Date(1245398693390)/,"Value":1},
{"Category":/Date(1245398693390)/,"Value":38},
{"Category":/Date(1245398693390)/,"Value":18}]
I suspect that the library is calling Category's toString() instead of the standard method. What can I do?

Try using the DateFormatHandling and DateTimeZoneHandling settings when you serialize, e.g.:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
List<GraphDataItem<DateTime, int>> items = GetItems();
var json = JsonConvert.SerializeObject(items, settings);

The default date serialization format used by JSON.Net is Microsoft date format. So you should be getting dates like you want. Ensure that while serializing, you're not using IsoDateTimeConverter.
This post about different date serialization options might be helpful.
Also, if you're using latest JSON.Net, you can use serialization tracing to see what's going on inside. That may turn up helpful clues as to why this is happening or how you can force it to produce correct output.
If everything else fails, you may have to resort to your a custom JsonConverter:
public class CustomConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
...
if(value is DateTime) {
var d = value as DateTime;
serializer.Serialize(writer, d);
}
else {
serializer.Serialize(writer, value);
}
...
}
// other overrides
}

Related

How to serialize enum with converter inside another custom converter

I have a custom JSON converter, because 3rd party API accept specific structure only.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bodyRequest = (IRequest) value;
writer.WriteStartObject();
writer.WritePropertyName(bodyRequest.RequestName);
writer.WriteStartObject();
writer.WritePropertyName("-xmlns");
writer.WriteValue("http://example.com");
writer.WritePropertyName(bodyRequest.WrapperName);
writer.WriteStartObject();
var contractResolver = new CamelCasePropertyNamesContractResolver {
IgnoreSerializableAttribute = false
};
var properties = value.GetType().GetProperties();
foreach(var propertyInfo in properties) {
if (HasAttribute(propertyInfo, typeof(JsonIgnoreAttribute))) {
continue;
}
var propValue = propertyInfo.GetValue(value);
var propertyName = contractResolver.GetResolvedPropertyName(propertyInfo.Name);
writer.WritePropertyName(propertyName);
writer.WriteValue(propValue);
}
writer.WriteEndObject();
writer.WriteEndObject();
writer.WriteEndObject();
}
So, I create strcuture with help of WriteXXX methods, then I get all properties and serialize them. All works fine, but I need to handle Enums. For example, we have the following request model:
public class ExampleRequest : IRequest
{
public long Id{ get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CarType CarType { get; set; }
public string RequestName => "Request";
public string WrapperName => "SendData";
}
public enum CarType
{
[EnumMember(Value = "NEW_CAR")
New,
[EnumMember(Value = "OLD_CAR")
Old
}
Currently, after serialization I see that CarType has numeric value 0 or 1,I understand that I use reflection and StringEnumConverter is ignored. How to properly serialize in this case?
Inside your loop, check whether the property has a JsonConverterAttribute applied. If so, get the type of converter, instantiate it and then call its WriteJson method instead of writing the value directly. Otherwise, just write the value as you are doing now.
In other words, replace this line:
writer.WriteValue(propValue);
with this:
var att = propertyInfo.GetCustomAttribute<JsonConverterAttribute>();
if (att != null)
{
var converter = (JsonConverter)Activator.CreateInstance(att.ConverterType);
converter.WriteJson(writer, propValue, serializer);
}
else
{
writer.WriteValue(propValue);
}

Why Json.NET cannot deserialize JSON arrays into object property?

Temporary note: This is NOT a duplicate of the above mentioned post
Let's say I have a server-side class structure like this.
public class Test
{
// this can be any kind of "Tag"
public object Data { get; set; }
}
public class Other
{
public string Test { get; set; }
}
Now a string like this is coming from let's say the client.
{"Data": [{$type: "MyProject.Other, MyProject", "Test": "Test"}] }
When I try to deserialize this into a Test instance, I get a result where the Tag property is a JToken instead of some kind of collection, for example ArrayList or List<object>.
I understand that Json.NET cannot deserialize into a strongly typed list, but I'd expect that it respects that it's at least a list.
Here is my current deserialization code.
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// this first assertion fails
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
I'm aware of the fact that if I serialize such a structure, then by default I'll get a { $type: ..., $values: [...]} structure in the JSON string instead of a pure array literal, and that will indeed properly deserialize. However, the client is sending a pure array literal, so I should be able to handle that in some way.
I managed to put together a JsonConverter to handle these kind of untyped lists. The converter applies when the target type is object. Then if the current token type is array start ([) it will force a deserialization into List<object>. In any other case it will fall back to normal deserialization.
This is a first version which passes my most important unit tests, however as I'm not a Json.NET expert, it might break some things unexpectedly. Please if anyone sees anything what I didn't, leave a comment.
public class UntypedListJsonConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
{
return serializer.Deserialize(reader);
}
return serializer.Deserialize<List<object>>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object);
}
}
Usage example:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = new[] { new UntypedListJsonConverter() }
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// now these assertions pass
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
Try this:
public class Test
{
public Dictionary<string, List<Other>> Data { get; } = new Dictionary<string, List<Other>>();
}
You need to set up the class you are trying to fill from json data to match as closely to the json structure. From the looks of it, the json looks a dictionary where the keys are strings and the values are arrays of Other objects.

Deserialize JSON object into C# class that wraps typed Dictionary field

I have a JSON object that I get from my REST API server that looks like this:
"settings": {
"assets.last_updated_at": "2016-08-24T23:40:26.442Z",
"data.version": 34
}
Normally I'd deserialize the object to a Dictionary of string to object, but I'd like to provide helper methods to get strongly typed versions of the values. I'm using JSON.NET to deserialize the JSON object. Here's what I've got so far:
[JsonConverter(typeof(SettingsJsonConverter))]
public class Settings
{
[JsonIgnore]
public Dictionary<string, string> Entries { get; private set; }
public Settings()
{
this.Entries = new Dictionary<string, string>();
}
}
In order to wrap the dictionary within a class, it seems like I needed to create a custom JsonConverter, which currently looks like this:
public class SettingsJsonConverter : JsonConverter
{
#region implemented abstract members of JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject settingsObj = JObject.Load(reader);
Settings settings = new Settings();
foreach(KeyValuePair<string, JToken> entry in settingsObj)
{
settings.Entries.Add(entry.Key, entry.Value.ToString());
}
return settings;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Settings);
}
#endregion
}
Now obviously I could create methods in my Settings class like, GetDate that takes the value for a given key and creates a new DateTime object with the ISO string value. However, JSON.NET already provides facilities to do what I want to do, so I'd rather use its built-in deserialization for any values I iterate over, instead of doing it myself.
It seems as though the place to do this would be inside the foreach loop in SettingsJsonConverter#ReadJson, but I'm not quite sure how to get the Type (not a JTokenType, a standard C# Type) of a given JToken so I can pass it into JToken#ToObject(Type). Or, how can I iterate over a JsonReader to populate my internal Entries dictionary with already deserialized values, such that I just need to do simple casts in my helper methods in Settings? Here's how I'd like my Settings class to look:
[JsonConverter(typeof(SettingsJsonConverter))]
public class Settings
{
[JsonIgnore]
public Dictionary<string, object> Entries { get; private set; }
public Settings()
{
this.Entries = new Dictionary<string, object>();
}
public DateTime GetDate(string key)
{
return (DateTime)this.Entries[key];
}
}
Am I even doing this correctly? It almost feels like there's some simple way I'm overlooking.
You can directly deserialize into a Dictionary<string, JToken> and use type casts as you need them so you don't need a Settings class or a custom converter. It doesn't look as nice as your solution, but the code is simpler:
var settings = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(json);
var date = (DateTime) settings["key"];
If you want to keep your Settings class, I'd suggest that you use the above line to deserialize the dictionary and create a new constructor in Settings with a dictionary parameter, so you end up with two constructors, one that creates a new dictionary, and one that uses a given dictionary to create the new one. (Don't re-use the parameter dictionary itself, you never know what the caller will do to the dictionary afterwards.)
public Settings(Dictionary<string, JToken> dict)
{
Entries = new Dictionary<string, JToken>(dict);
}
By the way, you can remove the JsonIgnore attribute because you have a custom converter class.
First of all we have to make a model for your corresponding json.
{
"settings": {
"assets.last_updated_at": "2016-08-24T23:40:26.442Z",
"data.version": 34
}
}
CSharp Model:
public class Settings
{
[JsonProperty("assets.last_updated_at")]
public string assets_last_updated_at { get; set; }
[JsonProperty("data.version")]
public int data_version { get; set; }
// Add other property here...
}
public class RootObject
{
public Settings settings { get; set; }
}
Deserialize Json string to Model :
RootObject rootObject = JsonConvert.DeserializeObject<RootObject>("Put your Json String");
Hope this help.

Serializing type information using a different JsonProperty in Json.Net

I have created several classes that map to Schema.org objects;
public class Thing {
public virtual string FullSchemaType { get { return "[schema.org]Thing"; } }
}
public class CreativeWork : Thing {
public override string FullSchemaType {get {return string.Join(":", base.FullSchemaType, "CreativeWork"); } }
[JsonProperty("author")]
public string Author { get;set; }
// etc
}
public class MediaObject : CreativeWork {
public override string FullSchemaType {get {return string.Join(":", base.FullSchemaType, "MediaObject"); } }
[JsonProperty("duration")]
public float? Duration { get;set; }
}
I have a factory class that creates e.g. a MediaObject, sets its properties. The FullSchemaType property is a Schema.org compliant way of noting its type. I am putting these objects into a database, serialising them using Json.NET 6.0.3.
I want to deserialize them into the correct C# objects. The standard Json.Net way to do this is to use TypeNameHandling - but that inserts a $type property into the serialised Json, which isn't ideal as we have several different applications interfacing with this database.
Is there a way to tell Json.NET to look at my FullSchemaType property for type binding information?
You can use JsonSerializerSettings to customise parameters of serialization: setting TypeNameHandling = TypeNameHandling.None allows you not to include $type information in serialized Json, and if you have any further problems with, say, customised converters you can use JsonSerializerSettings.IList<JsonConverter> settings to specify ones.
More detailed info is here: JsonSerializerSettings
and here: Newtonsoft.Json.TypeNameHandling
And here is a good example of JsonCreationConverter, you can overrride method ReadJson in your own converter, based on JsonConverter like this:
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
// Load JObject from stream
JObject jObject = JObject.Load(reader);
if (jObject[FullSchemaType] == {your_media_object_type_name})
MediaObject target = Create(objectType, jObject);
else if (jObject[FullSchemaType] == {your_creative_work_type_name})
CreativeWork target = Create(objectType, jObject);
else
Thing target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}

JsonConvert attributes appear to be getting skipped over. Why?

I have a number of properties that I'm trying to use Json.NET to serialize. Complex objects work fine as all I have to put above them are the [JsonProperty("name")] attributes.
Unfortunately, I have been struggling to get this to work with any enums. Ideally, I'd like to be able to define the property by just saying something like:
MyValue = ThisEnum.First;
and have the property within my class automatically serialize the enum to the appropriate changed value name (as this question asked: Control enum value format during (de)serialization via attributes. Unfortunately, I have written a number of custom converters and attempted to apply the suggestions of the people in one of my previous questions (Can't get enum to convert to json properly using Json.NET) and despite the respondents in that thread giving answers that look like they'd work, in my solution, they don't have any effect whatsoever.
For example, given this class of properties:
public class Animal
{
[JsonProperty("id")]
public string Id {get;set;}
[JsonProperty("types")]
[JsonConverter(typeof(StringEnumConverter))]
public AnimalType Types {get;set;}
[JsonProperty("created")]
[JsonConverter(typeof(MyDateTimeToSecondsSinceEpochConverter))]
public DateTime Created {get;set;}
}
Within the AnimalType enum:
public enum AnimalType
{
[EnumMember(Value="black_rhino")]
BlackRhino,
[EnumMember(Value="wild_pig")]
WildPig,
[EnumMember(Value="chicken")]
Chicken
}
Within the MyDateTimeToSecondsSinceEpochConverter class:
public class MyDateTimeToSecondsSinceEpochConverter : DateTimeConverterBase
{
public override WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(#"""\/Date(" + ConvertDateTimeToEpoch((DateTime)value).ToString() + #")\/""");
}
public override ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null) return null;
if (reader.TokenType == JsonToken.Integer)
return ConvertEpochToDateTime((long)reader.Value);
return DateTime.Parse(reader.Value.ToString());
}
public DateTime ConvertEpochToDateTime(long seconds)
{
return new DateTime(1970, 1, 1).AddSeconds(seconds);
}
public long ConvertDateTimeToEpoch(DateTime dateTime)
{
var epochStart = new DateTime(1970,1,1);
if (dateTime < epochStart) return 0;
return Convert.ToInt64(dateTime.Subtract(epochStart).TotalSeconds);
}
}
In my calling method, I can have something like:
var data = new Animal {
Id = "123abc",
Types = AnimalType.BlackRhino,
Created = new DateTime(2014,3,15)
}
I use a method through which I step through each of the properties of the object with the simple intention of listing each of them and the HTML encoded output of the value and putting them all in a string (that I'll later attach to a URL):
public static string GetString(Animal animal)
{
var result = "";
foreach( var property in animal.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
{
foreach(var attribute in property.GetCustomAttributes(false))
{
var name = (JsonPropertyAttribute)attribute;
var value = property.GetValue(animal, null);
if (value != null)
{
if (!string.Empty(result))
result += "&";
result += string.Format("{0}={1}", name.PropertyName, HttpUtility.UrlEncode(value.ToString()));
}
}
}
return result;
}
Unfortunately, when I run this, I see:
'id=abc123&type=BlackRhino&created=3%2f15%2f2014+12%3a00%3a00+AM'
Does anyone have any ideas as to how I can get the two JsonConverters to actually do some converting? The JsonProperty name is obviously changing, but 'BlackRhino' didn't change to 'black_rhino' nor did the DateTime convert to a long.
I'm ideally looking for something in attribute land or using these converters so that I don't have to write a get/set for every single enum I use in the project down the road, but can rather just apply the attribute and be done with it.
Thanks!
You can have Json.NET turn your object into a string and then into a dictionary, which is a collection of name/value pairs like a query string is.
public static string GetString(Animal animal)
{
var result = "";
var serialized = JsonConvert.SerializeObject(animal);
var dict = JsonConvert.DeserializeObject<IDictionary<string, string>>(serialized);
foreach (var pair in dict)
{
if (!string.IsNullOrEmpty(result))
result += "&";
result += string.Format("{0}={1}", pair.Key, HttpUtility.UrlEncode(pair.Value.ToString()));
}
return result;
}
With your example, this is:
id=123abc&types=black_rhino&created=%2fDate(1394841600)%2f

Categories