-2

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?

amitairos
  • 2,907
  • 11
  • 50
  • 84
  • Can't you just accept both? – Marco Salerno Sep 08 '17 at 11:41
  • @MarcoSalerno How? I have one Json array that has all of the items, how can I put in both types? – amitairos Sep 08 '17 at 11:42
  • Try catch, you try one type if it catches an error, you try the other one – Marco Salerno Sep 08 '17 at 11:44
  • @MarcoSalerno Yes, currently I have the list as the old type, and I want to add to it an object that is of the new type. How do I do this? – amitairos Sep 08 '17 at 11:45
  • [Click](https://stackoverflow.com/a/13945074/1997232). – Sinatr Sep 08 '17 at 11:48
  • You don't , you do 2 lists – Marco Salerno Sep 08 '17 at 11:49
  • 1
    *"while the users with the old version will still be able to use the app"* - this is backward compatibility, it is the easiest versioning to implement/maintain. Since you didn't provide the code I can't answer more specifically. – Sinatr Sep 08 '17 at 12:00
  • @Sinatr My new object is exactly the same as the old one, just with new properties. But how does that help me? What code should I provide? – amitairos Sep 08 '17 at 12:02
  • 2
    If you are just adding new properties, why is this even a problem? Just add those new properties to your `QuestionItem` type. For new JSON objects, they will be filled, for old ones they won’t. – poke Sep 10 '17 at 12:52
  • is your properties are dynamic? where are you adding your properties in JSON object or in c# object. – Manoj Pilania Sep 10 '17 at 12:56
  • @poke The problem is that apps that have only the old QuestionItem won't be able to deserialize the new one, and apps with the new QuestionItem won't be able to deserialize the old one. – amitairos Sep 10 '17 at 12:59
  • @Manoj I add the new properties in the C# object and then serialize it to JSON – amitairos Sep 10 '17 at 13:02
  • 2
    I don’t understand. JSON decoders are usually able to handle extra properties or missing properties just fine. Maybe you should add some code to show what you’re actually doing and how just expanding your type does not work for you. – poke Sep 10 '17 at 13:02
  • @poke I am using JSON.NET var list = JsonConvert.DeserializeObject>. When the json isn't a QuestionItem as defined in the code, it gives an error as far as I tested. – amitairos Sep 10 '17 at 13:06
  • 3
    I cannot really believe that since I’m able to run `JsonConvert.DeserializeObject>("[{},{}]")` just fine for any definition of `Test`. Show your code. – poke Sep 10 '17 at 13:11
  • @poke Thanks, I checked, and you were right. If I don't change the existing properties, and only add new ones, it deserializes the object fine. The problem is, if a user with the Old QuestionItem adds an object to the list, it first deserializes the whole list as the old QuestionItem list, adds the new item, and then serializes it back, which means even if there is a new QuestionItem there, it erases the new properties in it and converts it to an old QuestionItem. – amitairos Sep 12 '17 at 14:26
  • No, I’m suggesting you switch to the new type *only*. So all consumers still using the old type will send an incomplete “new” object which will be filled with default values (or you could even have some explicit fallback to complete those). But from then on, you only deal with the new object. – poke Sep 12 '17 at 14:33
  • @poke The problem is that I don't have access to the app on the consumer side, so unless he updates his app, he will convert all of the new types to old types. How do I prevent this? – amitairos Sep 12 '17 at 15:51
  • We’re still only talking about JSON objects, right? There’s no “type”. A JSON object is just a collection of key/value pairs. So there’s no old or new type, just more or fewer properties that get interpreted. – poke Sep 12 '17 at 16:05
  • @poke Yes, but in order to add new objects to the JSON list, I convert it to a c# object, add it to the deserialized list that is of type QuestionItem, then convert the whole thing back to JSON. In this process, if the user is using an old version, it will convert everything to the old QuestionItem. – amitairos Sep 12 '17 at 16:27
  • And that could be avoided *how* exactly? If you want older clients being able to interact with your API, maintaining support for the old type, then you cannot just make them use a new type they don’t know. – poke Sep 12 '17 at 17:13
  • @poke Isn't there a way to add a new object to JSON without first deserializing the old list and serializing it back? That would solve my problem. The problem is that I don't have control for determining everybody updates. Enough if one user didn't update, and he reverts the whole list to the old type. – amitairos Sep 12 '17 at 17:35
  • 1
    Sure, you do not have to map a JSON object to a statically typed object. You can just keep it as a dynamic JSON object. But just because *you* do it, does not mean that those other users will do it in their code. And you already said that you won’t get those to update anything, so you’re out of luck there. – poke Sep 12 '17 at 17:51

3 Answers3

6

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.

Jirajha
  • 483
  • 3
  • 10
0

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

Kodaloid
  • 1,211
  • 11
  • 19
0

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.

Dev
  • 166
  • 1
  • 9