soapergem
soapergem

Reputation: 9989

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?

Upvotes: 1

Views: 916

Answers (1)

dbc
dbc

Reputation: 116615

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

Upvotes: 1

Related Questions