1

I am trying to update the name of a property in a Json serializable class that is already released, so I need to make it backwards compatible.

public class myClass
{
    //[JsonIgnore] - now old json files can't be read, so this won't work...
    //[JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Error)] - didn't do anything
    //[JsonProperty(nameof(NewName)] - throws error - "That already exists"
    [Obselete("Use NewName instead")]
    public List<string> OldName { get => newName; set => newName = value; }

    public List<string> NewName { get; set; } = new List<string>();
}

And I use it like this:

[Test]
public void test()
{
    var foo = new myClass()
    {
        OldName = { "test" },
    };

    var bar = JsonConvert.SerializeObject(foo);
    var result = JsonConvert.DeserializeObject(bar, typeof(myClass));
}

When I look at the value in result.NewName, I find this list: {"test", "test"}, but I expected this list: {"test"}

The desired behavior:

  • If someone is already using OldName in their code, they get an obselete warning
  • if someone parses an old json file with OldName in it, it's correctly mapped to NewName
  • New json files that are created use NewName, and OldName isn't found anywhere in the json file
  • In no case is the value deserialized twice and put in the same list

How would you accomplish this?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
bwall
  • 984
  • 8
  • 22
  • I would create custom converter for this type. – Guru Stron Oct 20 '21 at 17:03
  • putting [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] over both OldName and NewName prevents it from appending to the list, but doesn't stop duplicated properties in the Json – bwall Oct 20 '21 at 22:21

3 Answers3

1

Try this

var foo = "{\"OldName\":[\"old test\"]}";
var fooN = "{\"NewName\":[\"new test\"]}";
var result = JsonConvert.DeserializeObject(foo, typeof(myClass));
//or
var result = JsonConvert.DeserializeObject(fooN, typeof(myClass));

var json = JsonConvert.SerializeObject(result);

json result:

{"NewName":["new test"]}
//or
{"NewName":["old test"]}

class

public class myClass
{
   [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<string> OldName {

        get {return null; }
        set {NewName=value;} 
    }

    public List<string> NewName {get; set;}
}
    
Serge
  • 40,935
  • 4
  • 18
  • 45
  • Fancy! Unfortunately in my case, I still need to allow people to have get access to the OldName property for now. But I'm sure you're answer will help others! Thanks for looking at it! – bwall Oct 20 '21 at 22:38
0

This works using System.Text.Json.

            var foo = new myClass()
            {
                OldName = { "test" },
            };

            var bar = JsonSerializer.Serialize<myClass>(foo);
            var result = JsonSerializer.Deserialize<myClass>(bar);
Hazrelle
  • 758
  • 5
  • 9
  • 1
    But the json string contains both values: {"OldName":["test","test2"],"NewName":["test","test2"]}. The deserializer may use an "append" mode for json array. That may explain that when OldName is deserialized, OldName has 2 values set (and so NewName), then the deserializer read NewName and append the 2 values from the json into the already 2 values of the List coming from OldName. – Hazrelle Oct 20 '21 at 17:08
  • 1
    True. I wonder if putting [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] over the properties would stop them from appending. – bwall Oct 20 '21 at 22:39
0

This answer was very helpful. Basically you refactor your code like this:

public class ControlReportResult : TargetReportResult
{
    public List<string> NewName { get; set; } = new();

    [JsonIgnore] // this stops the serializer from paying attention to this property
    [Obsolete("Use NewName instead")]
    public List<string> OldName => { get => NewName; set => NewName = value; }
    
    [JsonProperty("OldName")] //This doesn't throw an error because the serializer was told to ignore the real OldName property
    private List<string> _jsonOldNameSetter { set => NewName = value; } // no getter, so we are never going to write OldName out.
}

Some notes:

  • _jsonOldNameSetter is private, so your interface is still clean
  • OldName is still usable, but marked as obselete
  • The code will read in json files with either OldName or NewName, but will only write files with NewName

It shouldn't happen, but if you still somehow end up with a json file with both an OldName and a NewName, add the attribute [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] above OldName and NewName - this way they will overwrite each other, rather than appending.

bwall
  • 984
  • 8
  • 22