JsonConvert string to Collection - c#

I get this json format from an API:
"url_service": "",
"Description": null,
"Target": "5,6",
"Category ": "2"
I'm trying to deserialize the json into a model. The trouble is with the "Target" field which is supposed to be an ICollection of int.
Here is my model:
public string Description{ get; set; }
public ICollection<int> Target{ get; set; }
public int Category { get; set; }
Is there a way to process the Json before it gets serialised in order to make a collection out of the comma separated string?

Instead of trying to change the deserialization logic, why not make it easier on yourself and just include a new property in your model?
public string Description{ get; set; }
public int Category { get; set; }
//Change this back to string, since that is what your JSON is
public string Target{ get; set; }
//Add a "TargetList" property that just reads from "Target" and turns into a List
public List<int> TargetList => Target.Split(',').Select(x => int.Parse(x)).ToList();
Be aware that there is no error handling in my code so you will have to modify accordingly.

Yes, have your C# class implement the ISerializable interface. There are OnDeserialized(), OnSerialized() functions you could implement.
Refer to .NET Deserialisation with OnDeserializing and OnDeserialized

Your JSON fragment does not describe a collection of integers but a string. The correct one would be
"Target": [ 5, 6 ],
which translates into a JSON schema of
"Target": {
"type": ["array"],
"items": { "type": "integer"}
},
If you don't have control of the result then create another property which will be a result of integers such as
private string _target;
public string Target { get { return _target; }
set {
_target = value;
Targets = Regex.Matches(_target, #"(\d+)")
.OfType<Match>()
.Select(mt => int.Parse(mt.Value))
.ToList();
public List<int> Targets { get; set; }

The Target field is of string type in your json, hence the serialiser will attempt to read it as a string.
You can use a converter to overrule that, for example using Newtonsoft Json.
Let's assume your data structure to be defined as follows:
public class Data {
public string Description{ get; set; }
public ICollection<int> Target{ get; set; }
public int Category { get; set; }
}
Then you may create your own JsonConverter as follows:
public class DataConverter : JsonConverter {
public override bool CanWrite => false;
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) {
var jObject = JObject.Load(reader);
var data = new Data();
if (jObject.TryGetValue("Description", out JToken jDescription)) {
data.Description = jDescription.Value<string>();
}
if (jObject.TryGetValue("Target", out JToken jTarget)) {
data.Target = ToTarget(jTarget, serializer);
}
if (jObject.TryGetValue("Category", out JToken jCategory)) {
data.Category = jCategory.Value<int>();
}
return req;
}
private static ICollection<int> ToTarget( JToken jTarget, JsonSerializer serializer ) {
int defaultValue = -1;
var target = new List<int>();
var collection = jTarget.Value<string>().Split(',');
foreach (string str in collection)
if (int.TryParse(str, out int number))
target.Add(number);
else
target.Add(defaultValue);
return target;
}
public override bool CanConvert(Type objectType) => objectType == typeof(Data);
}
You can then deserialise using the following code:
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new DataConverter);
Data data = JsonConvert.DeserializeObject<Data>(yourJsonString, jsonSettings);
Further considerations: this approach provides clear separation between your data definition and the data parsing logic, hence keeping your data structure and definition clean from any json specific information and parsing logic.

Related

Deserializing a JSON array with awkward additional property in the schema

I have a very similar issue to this question here, except my application is in C#, and I can't figure out how to convert the solution unfortunately. I am trying to deserialize a JSON result that looks like this:
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77],
],
"last":1619118000
}
I use the following classes:
public class ResponseBase
{
[JsonProperty(PropertyName = "error")]
public List<string> Error;
}
public class OHLCResponse : ResponseBase
{
[JsonProperty("result")]
public OHLCResult Result;
}
public class OHLCResult
{
[JsonProperty("pair_names")]
public Dictionary<string, OHLC[]> GetHistory;
[JsonProperty("last")]
public long Last;
}
.... and then finally the guts of it:
public class OHLC
{
public int Time;
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
public decimal Vwap;
public decimal Volume;
public int Count;
}
I have a standard deserializer class which works for all other calls I am using to the same API, but I cannot get this call to work. When I retrieve OHLCResponse object,I don't get an error, and "Result.Last" is always populated, but the expected array of OHLC items in "Result.GetHistory" is always empty/null. I know that the data has been returned successfully since I can see the data in the variable returned from the WebRequest that I am then passing to the deserializer function, so it must be that I have these classes laid out incorrectly I guess.
Can anyone see what I'm doing wrong?
Many thanks in advance, Dave
The object you posted isn't valid JSON. The outside curly braces are missing. So I am going to assume it should look like this:
{
"error": [],
"result": {
"MANAEUR": [
[1619042400, "1.11200", "1.13488", "1.08341", "1.10077", "1.09896", "58878.56534370", 137],
[1619046000, "1.09767", "1.12276", "1.08490", "1.11097", "1.10456", "25343.25910419", 77],
],
"last": 1619118000
}
}
Anonymous Deserialization
The first method you could do, which may be a tad kludgy since you have to deserialize twice, is use anonymous deserialization.
Let's start by defining some models:
public sealed class OHLCModel
{
public long Time { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Vwap { get; set; }
public decimal Volume { get; set; }
public int Count { get; set; }
}
public sealed class ResultModel
{
[JsonIgnore]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
public sealed class RootModel
{
[JsonProperty("error")]
public List<string> Error { get; set; }
[JsonProperty("result")]
public ResultModel Result { get; set; }
}
As you can see we are ignoring the Manaeur object when serialization happens.
To make this method work, we'd do this:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
// First, just grab the object that has the mixed arrays.
// This creates a "template" of the format of the target object
var dto = JsonConvert.DeserializeAnonymousType(json, new
{
Result = new
{
Manaeur = new List<List<object>>()
}
});
// Next, deserialize the rest of it
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
// transfer the DTO using a Select statement
fullObject.Result.Manaeur = dto.Result.Manaeur.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
This isn't the most ideal solution as you are tightly coupling to the model in a few spots. The ideal way to do this would be to make a custom JsonSerializer.
Use a Custom JsonConverter
First thing we do is change your ResultModel to look like this:
public sealed class ResultModel
{
[JsonConverter(typeof(ManaeurJsonConverter)), JsonProperty("MANAEUR")]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
Then implement a JsonConverter:
public sealed class ManaeurJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => false; // this will never get called
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var lst = JArray.Load(reader).ToObject<List<List<object>>>();
return lst.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ // we don't need to write
throw new NotImplementedException();
}
}
You can then simply call it as so:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
Your JSON is not valid, I had to modify it to make it valid JSON (https://jsonformatter.org/). I added the root brackets and removed the comma delimter after the second inner array entry.
Valid JSON:
{
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77]
],
"last":1619118000
}
}
After updating the JSON, I used Visual Studio's 'Paste Special' to generate C# objects from the JSON. The following classes were created.
public class RootObject
{
[JsonProperty("error")]
public object[] Error { get; set; }
[JsonProperty("result")]
public Result Result { get; set; }
}
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
With the above JSON and classes, I used the following to deserialize the JSON.
string json = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);
EDIT:
To handle the MANAEUR property where the key label can be different.
Create a JsonConverter...
public class ManaeurConverter : JsonConverter
{
private Dictionary<string, string> propertyMappings { get; set; }
public ManaeurConverter()
{
this.propertyMappings = new Dictionary<string, string>
{
{"NOTMANAEUR","MANAEUR"}
};
}
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)
{
object instance = Activator.CreateInstance(objectType);
var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
if (!propertyMappings.TryGetValue(jp.Name, out var name))
name = jp.Name;
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override bool CanConvert(Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override bool CanWrite => false;
}
... Add the JsonConverter attribute to the class...
[JsonConverter(typeof(ManaeurConverter))]
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
... and parse like so...
string json_Manaeur = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
string json_Not_Manaeur = "{\"error\":[],\"result\":{\"NOTMANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var objManaeur = JsonConvert.DeserializeObject<RootObject>(json_Manaeur);
var objNotManaeur = JsonConvert.DeserializeObject<RootObject>(json_Not_Manaeur);

System.Text.Json - Deserialize nested object as string

I'm trying to use the System.Text.Json.JsonSerializer to deserialize the model partially, so one of the properties is read as string that contains the original JSON.
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Info { get; set; }
}
The example code
var json = #"{
""Id"": 1,
""Name"": ""Some Name"",
""Info"": {
""Additional"": ""Fields"",
""Are"": ""Inside""
}
}";
var model = JsonSerializer.Deserialize<SomeModel>(json);
should produce the model, which Info property contains the Info object from the original JSON as string:
{
"Additional": "Fields",
"Are": "Inside"
}
It doesn't work out of the box and throws an exception:
System.Text.Json.JsonException: ---> System.InvalidOperationException:
Cannot get the value of a token type 'StartObject' as a string.
What have I tried so far:
public class InfoToStringConverter : JsonConverter<string>
{
public override string Read(
ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
return reader.GetString();
}
public override void Write(
Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
and apply it in the model as
[JsonConverter(typeof(InfoToStringConverter))]
public string Info { get; set; }
and add in the options to JsonSerializer
var options = new JsonSerializerOptions();
options.Converters.Add(new InfoToStringConverter());
var model = JsonSerializer.Deserialize<SomeModel>(json, options);
Still, it throws the same exception:
System.Text.Json.JsonException: ---> System.InvalidOperationException:
Cannot get the value of a token type 'StartObject' as a string.
What is the right recipe to cook what I need? It worked in a similar way using Newtonsoft.Json.
Update
For me it is important to keep the nested JSON object as original as possible. So, I'd avoid options like to deserialize as Dictionary and serialize back, because I'm afraid to introduce undesirable changes.
Found a right way how to correctly read the nested JSON object inside the JsonConverter. The complete solution is the following:
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(InfoToStringConverter))]
public string Info { get; set; }
}
public class InfoToStringConverter : JsonConverter<string>
{
public override string Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (var jsonDoc = JsonDocument.ParseValue(ref reader))
{
return jsonDoc.RootElement.GetRawText();
}
}
public override void Write(
Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
In the code itself there is no need even to create options:
var json = #"{
""Id"": 1,
""Name"": ""Some Name"",
""Info"": {
""Additional"": ""Fields"",
""Are"": ""Inside""
}
}";
var model = JsonSerializer.Deserialize<SomeModel>(json);
The raw JSON text in the Info property contains even extra spaces introduced in the example for nice readability.
And there is no mixing of model representation and its serialization as remarked #PavelAnikhouski in his answer.
You can use a JsonExtensionData attribute for that and declare a Dictionary<string, JsonElement> or Dictionary<string, object> property in your model to store this information
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
[JsonIgnore]
public string Data
{
get
{
return ExtensionData?["Info"].GetRawText();
}
}
}
Then you can add an additional property to get a string from this dictionary by Info key. In code above the Data property will contain the expected string
{
"Additional": "Fields",
"Are": "Inside"
}
For some reasons adding the property with the same name Info doesn't work, even with JsonIgnore. Have a look at Handle overflow JSON for details.
You can also declare the Info property as JsonElement type and get raw text from it
public class SomeModel
{
public int Id { get; set; }
public string Name { get; set; }
public JsonElement Info { get; set; }
}
var model = JsonSerializer.Deserialize<SomeModel>(json);
var rawString = model.Info.GetRawText();
But it will cause a mixing of model representation and its serialization.
Another option is to parse the data using JsonDocument, enumerate properties and parse them one by one, like that
var document = JsonDocument.Parse(json);
foreach (var token in document.RootElement.EnumerateObject())
{
if (token.Value.ValueKind == JsonValueKind.Number)
{
if(token.Value.TryGetInt32(out int number))
{
}
}
if (token.Value.ValueKind == JsonValueKind.String)
{
var stringValue = token.Value.GetString();
}
if (token.Value.ValueKind == JsonValueKind.Object)
{
var rawContent = token.Value.GetRawText();
}
}
a quick addendum to the accepted answer:
If you need to write raw JSON values as well, here is an implementation of the Write method for the converter:
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.Parse(value))
{
document.RootElement.WriteTo(writer);
}
}
As outlined in the dotnet runtime repo on github, this seems to be the "proper" way to workaround the fact that they decided not to implement a WriteRawValue method.

Is there a way to make Json.Net deserialize to a concrete type for a property declared as "object"

Let's say I have a JSON document that looks like this:
{
"Id" : "233124",
"RequestDate" : "2019-11-25T10:00:00"
"RequestPayload" : {
"Id" : "123abc",
"Url" : "http://blah.example/api/action",
"PostData" : "insert random post data here"
}
}
And I have a couple of C# POCOs that look like this:
public class RequestLog {
public string Id { get; set; }
public DateTime RequestDate { get; set; }
public object RequestPayload { get; set; }
}
public class RequestPayload {
public string Id { get; set; }
public string Url { get; set; }
public string PostData { get; set; }
}
Is there a way to have Json.Net deserialize the document such that the RequestLog.RequestPayload property is of type RequestPayload, even though it is declared as object? The goal would be to cast it back to RequestPayload in order to manipulate it, like this:
var result = JsonConvert.DeserializeObject<RequestLog>(json, ...);
var requestPayload = (RequestPayload)request.RequestPayload;
// do other stuff with requestPayload here
I can't change the declaration of RequestLog, as it is in a NuGet package I don't control. I looked into creating a custom converter, and it seems like it should be possible, but I am utterly stumped as to how to pull it off.
You can use a custom converter for your RequestLog class:
public class RequestLogConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(RequestLog);
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
// Start by running the default serialisation
var log = new RequestLog();
serializer.Populate(jObject.CreateReader(), log);
// Manually deserialize RequestPayload
log.RequestPayload = jObject["RequestPayload"].ToObject<RequestPayload>();
return log;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Which can be used as follows:
JsonConvert.DeserializeObject<RequestLog>(json, new JsonSerializerSettings
{
Converters = new JsonConverter[] { new RequestLogConverter() }
});
If you must declare this RequestPayload property of the RequestLog as an object, and you don't need this code to be reusable, you can brute force this.
JContainer json = GetMySerializedContentAsJson(content);
RequestPayload payload = JsonConvert.DeserializeObject<RequestPayload>(json["RequestPayload"]);
RequestLog log = JsonConvert.DeserializeObject<RequestLog>(json);
log.RequestPayload = payload;//=>you should now have your object.
In your RequestLog class,
Change:
public object RequestPayload { get; set; }
to:
public RequestPayload RequestPayload { get; set; }
And then you'll be able to use it after deserialization, without having to cast it:
var result = JsonConvert.DeserializeObject<RequestLog>(json, ...);
var requestPayload = result.RequestPayload;
// do other stuff with requestPayload here

Serializing JSON array of 2 doubles to strongly typed object

I'm using Newtonsoft.JSON and I have a JSON array where each array member is itself an array of two numbers, e.g.
{
"objList": [
[8.8, 4],
[9.2, 1.45],
[1.61, 32.79]
]
}
I want to deserialize this into a class MyObjList defined by
public class MyObjList {
[DataMember(Name = "objList")]
public List<MyObj> ObjList { get; set; }
}
where MyObj is the class
public class MyObj {
public double FirstDouble{ get; set; }
public double SecondDouble { get; set; }
}
I knew that more work was required, but just tying the above gave me a helpful error message that told me that I could put [JsonArrayAttribute] on the MyObj class, i.e.
[JsonArrayAttribute]
public class MyObj{
public double FirstDouble{ get; set; }
public double SecondDouble { get; set; }
}
That has moved things on and the deserializer now fails at the next stage where it's looking for a suitable constructor. But I don't know how to write the constructor that is required and I can't find any examples. Can someone tell me how to do it?
If you're keen on using MyObj instead of List<double>, you could try by creating a custom JsonConverter for your MyObj type — which basically just wraps the whole List<double> deserialization and feeding it to the MyObj type — like this:
[JsonConverter(typeof(MyObjConverter))]
public class MyObj{
public double FirstDouble{ get; set; }
public double SecondDouble { get; set; }
}
public class MyObjConverter : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType) {
return objectType == typeof(MyObj);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var doubles = serializer.Deserialize<List<double>>(reader);
return new MyObj { FirstDouble = doubles[0], SecondDouble = doubles[1] };
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
Demo: https://dotnetfiddle.net/x0FCFX
The JSON you have, does not represent you object MyObjList.
{
"objList": [
[8.8, 4],
[9.2, 1.45],
[1.61, 32.79]
]
}
Your JSON is basically a List<List<double>>.
One easy way to create your object out of this data, is to use Linq-to-JSON from Json.NET and construct your object like this
var jsonString = #"{ ""objList"": [ [8.8, 4], [9.2, 1.45], [1.61, 32.79] ] }";
var jObject = JObject.Parse(jsonString);
var myObjList = new MyObjList
{
ObjList = jObject["objList"]
.ToObject<List<List<double>>>()
.Select(list => new MyObj { FirstDouble = list[0], SecondDouble = list[1] })
.ToList()
};
See the documentation for more details.
Don't know if it is a requirement mapping your doubles into a actual class like that. I think doing that would only complicate things. This is the simplest way to get it working:
MyObjList
public class MyObjList
{
public List<List<double>> objList { get; set; }
}
And in a POST you will get this:

How can I use Json.net to populate a model with custom bindings/mappings?

Here's my JSON:
{
"macAddress": "000A959D6816",
"softwareVersion": "1.2.1.5-UnnecessaryInfo",
"lastUpdated": "2015-04-03 20:46:40.375 -0500",
"charging": true
}
And using Json.NET, I can do the following in C#:
namespace JsonTest
{
public class Tablet
{
public string MacAddress { get; set; }
public string SoftwareVersion { get; set; }
public DateTime LastUpdated { get; set; }
public bool Charging { get; set; }
}
public class TestClass
{
public void Test()
{
var json = "{ ... }"; // filled in with JSON info from above
var model = new Tablet();
try
{
JsonConvert.PopulateObject(json, model);
}
catch (JsonSerializationException ex)
{
Console.WriteLine(ex);
}
}
}
}
So far, so good. The code I have here works great. It populates my model object with all the data from the Json. However, I don't really want the SoftwareVersion property of my model to be a string; I'd rather have it be an instance of the System.Version class. In other words, I'd like my Tablet class to look more like this:
public class Tablet
{
public string MacAddress { get; set; }
public Version SoftwareVersion { get; set; }
public DateTime LastUpdated { get; set; }
public bool Charging { get; set; }
}
I don't care about the unnecessary info that gets appended on the end of that version string, so I'd like to write some sort of mapper/binder class that examines the version string from the Json, strips off the unnecessary info, and then parses that field into a Version object before proceeding to populate my model. I do know how to write that individual parsing method; this would do the trick:
private static Version ParseVersion(object versionObj)
{
var pattern = new Regex(#"^[\d.]+");
var versionString = versionObj.ToString();
if (!pattern.IsMatch(versionString)) return null;
var match = pattern.Match(versionString);
versionString = match.Groups[0].ToString();
Version version;
Version.TryParse(versionString, out version);
return version;
}
What I don't know is where and how to "plug this in" during the JsonConvert process. I see that the PopulateObject takes an optional JsonSerializerSettings parameter, and in turn that has several different object initializer parameters like Binder and Converters. But I'm not sure which one to use, nor how to write either of those classes to do what I'm describing here. How do I do it? And what is the difference between a Binder and a Converter?
Just add an appropriate JsonConverterAttribute to your Version property, and PopulateObject will use it:
public class Tablet
{
public string MacAddress { get; set; }
[JsonConverter(typeof(VersionConverter))]
public Version SoftwareVersion { get; set; }
public DateTime LastUpdated { get; set; }
public bool Charging { get; set; }
}
And here is the actual converter:
public class VersionConverter : JsonConverter
{
private static Version ParseVersion(object versionObj)
{
var pattern = new Regex(#"^[\d.]+");
var versionString = versionObj.ToString();
if (!pattern.IsMatch(versionString))
return null;
var match = pattern.Match(versionString);
versionString = match.Groups[0].ToString();
Version version;
Version.TryParse(versionString, out version);
return version;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(System.Version);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return ParseVersion((string)token);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var version = (Version)value;
if (version != null)
writer.WriteValue(value.ToString());
}
}
You should now be all set.
Alternatively, if you have a Version property appearing in many different container classes in a complex object graph, and you don't want to set the JsonConverterAttribute everywhere, you can add your converter to JsonSerializerSettings.Converters, then pass that to PopulateObject:
JsonConvert.PopulateObject(json, model, new JsonSerializerSettings { Converters = new JsonConverter [] { new VersionConverter() } } );

Categories

Resources