JSON Deserializing modified class. Custom JSON Deserialize method

时间:2019-04-16 22:46:39

标签: c# json json.net

I had a class that I was regularly serializing and deserializing. One of its fields used to be a string to represent a road, but has since been changed to its own class.

class Foo
{
    public string RoadRef;
}

Has now been changed to:

class Foo
{
    public Road RoadRef;
}

class Road
{
    public Road(string val)
    {
        Lane = val[0];
        Number = int.Parse(val.Substring(1));
    }

    public char Lane;
    public int Number = 1;
}

Now I'm getting errors when I try to deserialize from strings that were serialized with the old version of the class. I have a lot of old serialized files that I don't want to go back and reserialize to the new format, especially since changes like this will likely happen again.

I should be able to specify a custom method to Deserialize (and Serialize) for JsonConvert.Deserialize right? Is there a better approach to deal with this problem?

The closest I've got so far is adding a [JsonConstructor] attribute above the constructor as suggested here, but it didn't help the deserializing crashes.

The original JSON looks like this: {"RoadRef":"L1"} It throws this exception:

Newtonsoft.Json.JsonSerializationException: 'Error converting value "L1" to type 'Road'. Path 'RoadRef', line 1, position 15.'

ArgumentException: Could not cast or convert from System.String to Vis_Manager.Road.

3 个答案:

答案 0 :(得分:1)

As stuartd suggest, you could use a custom JsonConverter to explicitly say how to convert a string to your data type.

void Main()
{
    string json = @"{""RoadRef"":""L1""}";
    var data = JsonConvert.DeserializeObject<Foo>(json);
}

public class RoadConverter : JsonConverter<Road>
{

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, Road value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override Road ReadJson(JsonReader reader, Type objectType, Road existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.ValueType != typeof(string))
            return (Road)serializer.Deserialize(reader, objectType);

        var data = reader.Value as string;
        return new Road(data);
    }
}


public class Foo
{
    [JsonConverter(typeof(RoadConverter))]
    public Road RoadRef { get; set; }
}

public class Road
{
    public Road(string val)
    {
        Lane = val[0];
        Number = int.Parse(val.Substring(1));
    }

    public char Lane { get; set; }
    public int Number { get; set; } = 1;
}

If you don't want to use Attribute decorator and keep your model out of any serialization concern, you can use it as parameter of DeserialzeObject method

var data = JsonConvert.DeserializeObject<Foo>(json, new RoadConverter());

答案 1 :(得分:1)

Rather than using a custom JsonConverter you could use the implicit operator to convert directly from a string:

public class Road
{
    public Road(string val)
    {
        Lane = val[0];
        Number = int.Parse(val.Substring(1));
    }

    public char Lane { get; set; }
    public int Number { get; set; } = 1;

    public static implicit operator Road(string val)
    {
        return new Road(val);
    }
}

答案 2 :(得分:0)

My solution was to add an overriding cast from string to the class. The JSON Deserialize method tries to explicitly cast the string value to the class, which is now possible.

public Road(string val)
{
    Lane = val[0];
    Number = int.Parse(val.Substring(1));
 }

public static explicit operator Road(string val)
{
    return new Road(val);
}