Sunday, September 17, 2017

Managing multiple versions of object in JSON

Leave a Comment

I have a class in C#, that has a number of variables. Let's call it "QuestionItem". I have a list of this object, which the user modifies, and then sends it via JSON serialization (with Newtonsoft JSON library) to the server. To do so, I deserialize the objects that are already in the server, as a List<QuestionItem>, then add this new modified object to the list, and then serialize it back to the server.

In order to display this list of QuestionItems to the user, I deserialize the JSON as my object, and display it somewhere.

Now, the problem is - that I want to change this QuestionItem and add some variables to it.

But I can't send this NewQuestionItem to the server, because the items in the server are of type OldQuestionItem.

How do I merge these two types, or convert the old type to the new one, while the users with the old version will still be able to use the app?

3 Answers

Answers 1

You are using an Object Oriented Language, so you might aswell use inheritance if possible.

Assuming your old QuestionItem to be:

[JsonObject(MemberSerialization.OptOut)] public class QuestionItem  {     [JsonConstructor]     public QuestionItem(int Id, int Variant)     {         this.Id = Id;         this.Variant = Variant;     }      public int Id { get; }     public int Variant { get; }     public string Name { get; set; } } 

you can extend it by creating a child class:

[JsonObject(MemberSerialization.OptOut)] public class NewQuestionItem : QuestionItem {     private DateTime _firstAccess;      [JsonConstructor]     public NewQuestionItem(int Id, int Variant, DateTime FirstAccess) : base(Id, Variant)     {         this.FirstAccess = FirstAccess;     }     public DateTime FirstAccess { get; } } 

Note that using anything different than the default constructor for a class requires you to use the [JsonConstructor] Attribute on this constructor and every argument of said constructor must be named exactly like the corresponding JSON properties. Otherwise you will get an exception, because there is no default constructor available.

Your WebAPI will now send serialized NewQuestionItems, which can be deserialized to QuestionItems. In fact: By default, JSON.NET as with most Json libraries, will deserialize it to any object if they have at least one property in common. Just make sure that any member of the object you want to serialize/desreialize can actually be serialized.

You can test the example above with the following three lines of code:

var newQuestionItem = new NewQuestionItem(1337, 42, DateTime.Now) {Name = "Hello World!"}; var jsonString = JsonConvert.SerializeObject(newQuestionItem); var oldQuestionItem = JsonConvert.DeserializeObject<QuestionItem>(jsonString); 

and simply looking at the property values of the oldQuestionItem in the debugger.

So, this is possible as long as your NewQuestionItem only adds properties to an object and does neither remove nor modify them.

If that is the case, then your objects are different and thus, requiring completely different objects with a different URI in your API, as long as you still need to maintain the old instance on the existing URI.

Which brings us to the general architecture:

The most clean and streamline approach to what you are trying to achieve is to properly version your API.

For the purpose of this link I am assuming an Asp.NET WebApi, since you are handling the JSON in C#/.NET. This allows different controller methods to be called upon different versions and thus, making structural changes the resources your API is providing depending on the time of the implementation. Other API will provide equal or at least similar features or they can be implemented manually.

Depending on the amount and size of the actual objects and potential complexity of the request- and resultsets it might also be worth looking into wrapping requests or responses with additional information. So instead of asking for an object of type T, you ask for an Object of type QueryResult<T> with it being defined along the lines of:

[JsonObject(MemberSerialization.OptOut)] public class QueryResult<T> {     [JsonConstructor]     public QueryResult(T Result, ResultState State,              Dictionary<string, string> AdditionalInformation)     {         this.Result = result;         this.State = state;         this.AdditionalInformation = AdditionalInformation;     }      public T Result { get; }     public ResultState State { get; }     public Dictionary<string, string> AdditionalInformation { get; } }  public enum ResultState : byte {     0 = Success,     1 = Obsolete,     2 = AuthenticationError,     4 = DatabaseError,     8 = .... } 

which will allow you to ship additional information, such as api version number, api version release, links to different API endpoints, error information without changing the object type, etc.

The alternative to using a wrapper with a custom header is to fully implement the HATEOAS constraint, which is also widely used. Both can, together with proper versioning, save you most of the trouble with API changes.

Answers 2

How about you wrapping your OldQuestionItem as a property of QuestionItem? For example:

public class NewQuestionItem {     public OldQuestionItem OldItem { get; set; }     public string Property1 {get; set; }     public string Property2 {get; set; }     ... } 

This way you can maintain the previous version of the item, yet define new information to be returned.

Koda

Answers 3

You can use something like

public class OldQuestionItem {   public DateTime UploadTimeStamp {get; set;} //if less then DateTime.Now then it QuestionItem    public string Property1 {get; set; }   public string Property2 {get; set; }   ...    public OldQuestionItem(NewQuestionItem newItem)   {      //logic to convert new in old   } }  public class NewQuestionItem : OldQuestionItem {  } 

and use UploadTimeStamp as marker to understand, what Question is it.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment