Deserialize list of JSON strings to find the correct model

Viewed 161

This is quite annoying and I only need to run this code ONCE. After that, all will get deleted (it's migration code)

I have a List<string> of JSON strings. They contain various different formats of JSON objects, which can look like this:

{
    "id": 123
}
{
    "id": "7521b497-abb7-46b8-bddc-177a6fd9f974",
    "folderId": 123
}
{
    "folderId": 123
}

and so on. I need to get the 123, which can be in the properties id and folderId. If I simply do:

class IdModel {
    public int Id { get; set; }
}

//inside a function
var model = JsonConvert.DeserializeObject<IdModel>(json);

it will fail when it gets to the second JSON, because id is a GUID. Instead, it would need to look for FolderId, which means I can do something like this:

class IdModel {
    public int Id { get; set; }
}

class FolderIdModel {
    public int FolderId { get; set; }
}

//inside a function
int folderId;

try {
    var model = JsonConvert.DeserializeObject<FolderIdModel>(json);
    folderId = model.FolderId;
} catch {
    var model = JsonConvert.DeserializeObject<IdModel>(json);
    folderId = model.Id;
}

That would be "fine" for this scenario, but I probably have 10 different JSON objects, that all look different. FolderId > Id, because I always know FolderId is the correct one, unless it has no FolderId, in which case it MIGHT have an Id (should explode if neither FolderId or Id is correct).

My question is: Is there a smart way to deserialize to different models, without looking at the JSON? Remember Id can both be a GUID and an integer, depending on the JSON objects.

I know this is really bad and I'm sorry.

4 Answers

Yes you can use the type dynamic where it will match the outcome of your Json object and then validate wheter the value type is a Guid or a int like so :

int folderId;
var model = JsonConvert.DeserializeObject<dynamic>(json);
folderId = model.id != null && model.id is int ? model.id : model.folderId;

If there are more possible outcomes you can break this ternary operator and validate them singulary.

I'd just deserialize both with the same class, then parse them according to the property values. For example:

public class IdModel
{
    public string Id { get; set; }
    public string FolderId { get; set; }

    public int Value
    {
        get
        {
            if (int.TryParse(Id, out int value))
            {
                return value;
            }
            else if (int.TryParse(FolderId, out value))
            {
                return value;
            }
            else
            {
                throw new Exception("This model has no valid id");
            }
        }
    }
}

Usage:

string json1 = "{\"id\": 123}";
string json2 = "{\"id\": \"7521b497-abb7-46b8-bddc-177a6fd9f974\",\"folderId\": 123}";
string json3 = "{\"folderId\": 123}";

IdModel model1 = JsonConvert.DeserializeObject<IdModel>(json1); // model1.Value = 123
IdModel model2 = JsonConvert.DeserializeObject<IdModel>(json2); // model2.Value = 123
IdModel model3 = JsonConvert.DeserializeObject<IdModel>(json3); // model3.Value = 123

I would add an extra Property to the Model which gets the real id (FolderId or Id, depending on which is set correctly). The model would look like this:

class Model
        {
            public string Id { get; set; }
            public int? FolderId { get; set; }
            public int RealFolderId
            {
                get
                {
                    if (FolderId != null)
                    {
                        return FolderId.Value;
                    }
                    int id;
                    if (int.TryParse(Id, out id))
                    {
                        return id;
                    }
                    throw new Exception("This explodes");
                }
            }
        }

The id is serialized as a string, so it will neither break when Id is a Guid nor when it is an int. The Exception is thrown when both, FolderId and Id have "incorrect values"; none is an int.

The rest of the code will be pretty simple:

var deserialized = JsonConvert.DeserializeObject<Model>(json);
int folderId = deserialized.RealFolderId;

You can try to use DeserializeAnonymousType method, something like that

var responseObject = JsonConvert.DeserializeAnonymousType(json, new { id = "" });

That convert it to required type (int or Guid) or use just object type

Related