Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize JSON where the property name indicates the type of the value

Tags:

c#

json.net

I'm currently dealing with getting data from an external API. The data I receive looks something like what is shown below. (Just a mockup; don't expect the values to make any sense. It's just to illustrate what kind of data I get.)

{
   "user": [
      {
        "key": "12345678",
        "data": [
          {
            "id": "Name",
            "string": "Bob"
          },
          {
            "id": "ElapsedTimeSinceLastMessage",
            "timestamp": 1618233964000
          },
          {
            "id": "Age",
            "number": 27
          }
        ]
      }
   ]
}

I don't really know how I should be going about deserializing this JSON.

The classes I'm using to deserialize right now look like this:

public class User
{
    [JsonProperty("key")]
    public string Key { get; set; }

    [JsonProperty("data")]
    public List<DataEntry> DataEntries { get; set; }
}

public class DataEntry
{
    [JsonProperty("id")]
    public string Id { get; set; }

    public Type Value { get; set; }
}

And I don't know what I need to set in order to deserialize the Value inside the DataEntry. Maybe someone can guide me into the right direction?

like image 942
P4NIK Avatar asked Dec 21 '25 15:12

P4NIK


2 Answers

The Data part of this JSON is really just a Dictionary<string, object> in disguise. You can use a custom JsonConverter to transform the list of id/value pairs into that format for easy use.

Frist, define these classes:

class RootObject
{
    [JsonProperty("user")]
    public List<User> Users { get; set; }
}

class User
{
    public string Key { get; set; }

    [JsonConverter(typeof(CustomDataConverter))]
    public Dictionary<string, object> Data { get; set; }
}

Next, define the converter:

class CustomDataConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Dictionary<string, object>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return JToken.Load(reader)
            .Children<JObject>()
            .ToDictionary(jo => (string)jo["id"],
                          jo => jo.Properties()
                                  .Where(jp => jp.Name != "id" && jp.Value is JValue)
                                  .Select(jp => ((JValue)jp.Value).Value)
                                  .FirstOrDefault());
    }

    public override bool CanWrite => false;

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

You can then deserialize and dump out the data like this:

var root = JsonConvert.DeserializeObject<RootObject>(json);
foreach (User user in root.Users)
{
    Console.WriteLine("User Key: " + user.Key);
    foreach (var kvp in user.Data)
    {
        Console.WriteLine(kvp.Key + ": " + kvp.Value);
    }
}

Here is a working demo: https://dotnetfiddle.net/GIT4dl

like image 171
Brian Rogers Avatar answered Dec 24 '25 06:12

Brian Rogers


One angle of attack would be with Dictionaries:

public class WithUser
{
    public List<User> User { get; set; }

}

public class User
{
    [JsonProperty("key")]
    public string Key { get; set; }

    [JsonProperty("data")]
    public List<Dictionary<string,object>> DataEntries { get; set; }
}

The extraction is a bit of a pain but possible:

public static void Main()
{
    var json = File.ReadAllText("Example.json");
    var x = JsonConvert.DeserializeObject<WithUser>(json);

    var user = x.User.Single();
    var age = Extract<long>(user, "Age");
    var name = Extract<string>(user, "Name");
    var elapsedTimeSinceLastMessage = TimeSpan.FromTicks(Extract<long>(user, "ElapsedTimeSinceLastMessage"));
    
}

public static T Extract<T>(User user, string name)
{
    var o = user.DataEntries
        .SingleOrDefault(d => (string)d["id"] == name) // Find the one with age
        .SingleOrDefault(kvp => kvp.Key != "id") // Find the not 'id' value
        .Value; // Take the value  
    return (T)o;
}
like image 36
tymtam Avatar answered Dec 24 '25 05:12

tymtam