Joshua Pena
Joshua Pena

Reputation: 81

Get the values of dynamic keys from a JSON string

I have this json string and i want to get the 4th line (iValue, sValue) of every record. My problem here is the keys vary for every record (based on the data type of the value).

Is there any way to do this on C#?

Here is an example:

{ "data": [
        {
          "pKey": "0",
          "Entity": "tableName",
          "Attribute": "CID",
          "iValue": "13"
        },
        {
          "pKey": "0",
          "Entity": "tableName",
          "Attribute": "username",
          "sValue": "test_user1"
        }] }

Upvotes: 4

Views: 6472

Answers (6)

Neville Nazerane
Neville Nazerane

Reputation: 7059

Here is kind of a big implementation, you will have to implement this for each iValue, fValue, etc however, it speeds up the implementation and usage. First of, here is the usage:

string rawJson = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";

var values = JsonConvert.DeserializeObject<TakeData>(rawJson).Data.Select(v => v.PureData);

Now values contains the list. Here is the usage for accessing each:

foreach (var val in values)
{
    if (val is IntData i)
    {
        int myInt = i.iValue;
        // use the rest of the properties
    }
    else if (val is StrData s)
    {
        string myStr = s.sValue;
        // use the rest of the properties
    }
}

And here is the implementation:

class TakeData
{
    public List<TakeItAll> Data { get; set; }
}

class TakeItAll
{

    public int pKey { get; set; }
    public string Entity { get; set; }
    public string Attribute { get; set; }

    private int _iValue;
    public int iValue
    {
        get => _iValue;
        set
        {
            _iValue = value;
            PureData = new IntData { pKey = pKey, Entity = Entity, Attribute = Attribute, iValue = iValue };
        }
    }

    private string _sValue;
    public string sValue
    {
        get => _sValue;
        set
        {
            _sValue = value;
            PureData = new StrData { pKey = pKey, Entity = Entity, Attribute = Attribute, sValue = sValue };
        }
    }

    public IPureData PureData { get; private set; }

}

interface IPureData
{
    int pKey { get; set; }
    string Entity { get; set; }
    string Attribute { get; set; }
}

class IntData : IPureData
{
    public int pKey { get; set; }
    public string Entity { get; set; }
    public string Attribute { get; set; }
    public int iValue { get; set; }
}

class StrData : IPureData
{
    public int pKey { get; set; }
    public string Entity { get; set; }
    public string Attribute { get; set; }
    public string sValue { get; set; }
}

Of course you can use some alternatives as well. Such as using an enum in TakeItAll to keep track of the data type (or a type variable) instead of so many classes. This way However the size of the values object would be larger.

class TakeItAll
{

    public int pKey { get; set; }
    public string Entity { get; set; }
    public string Attribute { get; set; }

    private int _iValue;
    public int iValue
    {
        get => _iValue;
        set
        {
            _iValue = value;
            ValType = typeof(string);
        }
    }

    private string _sValue;
    public string sValue
    {
        get => _sValue;
        set
        {
            _sValue = value;
            ValType = typeof(int);
        }
    }

    public Type ValType { get; private set; }

}

Upvotes: 1

Omar Muscatello
Omar Muscatello

Reputation: 1301

My two cents:

Get each object of the array as a KeyValuePair

var json = "your json here";

var root = JsonConvert.DeserializeObject<Root>(json);

foreach (var element in root.Data)
{
    //===================================================> Here using object because your value type change. You can change it to string if your value is always wrapped in a string (like "13")
    var keyValuePair = element.ToObject<Dictionary<string, object>>();

    //here, for each object of the 'data' array, you can check if the desidered property exists
    if (keyValuePair.ContainsKey("iValue"))
    {
        var propertyValue = keyValuePair["iValue"];
    }
    else if (keyValuePair.ContainsKey("sValue"))
    {
        var propertyValue = keyValuePair["sValue"];
    }

    // Or you can check the property name in the desidered position
    if (keyValuePair.Keys.ElementAt(3) == "iValue")
    {
        var propertyValue = keyValuePair["iValue"];
    }
    else if (keyValuePair.Keys.ElementAt(3) == "sValue")
    {
        var propertyValue = keyValuePair["sValue"];
    }
}

Where Root is

class Root
{
    [JsonProperty("data")]
    public List<JObject> Data { get; set; }
}

With this solution you can always know which property (iValue or sValue) is specified. On the contrary, if you use a model class which has both property names, you wouldn't know which property is specified when the value is null (unless you use additional properties/classes and a custom JsonConverter).

Edit

As Panagiotis Kanavos reminded me, the JObject class implements IDictionary<string, JToken>. So, inside your foreach you could use:

if (element["iValue"] != null)
{
    var propertyValue = element["iValue"].Value<string>();
}
if (element["sValue"] != null)
{
    var propertyValue = element["sValue"].Value<string>();
}

// Or you can check the property name in the desidered position
var propName = element.Properties().ElementAt(3).Name;

if (propName == "iValue")
{
    var propertyValue = keyValuePair["iValue"].Value<string>();
}
else if (propName == "sValue")
{
    var propertyValue = keyValuePair["sValue"].Value<string>();
}

Of course you can optimize this code and check for nulls.

Upvotes: 0

usselite
usselite

Reputation: 816

You could load the Json in a ExpandoObject

var expConverter = new ExpandoObjectConverter();
dynamic objList = JsonConvert.DeserializeObject<List<ExpandoObject>>(json, expConverter);

JSON array to ExpandoObject via JSON.NET

Then once you have loaded it in as a List<ExpandoObject> you may itterate over it as a dictionary.

    foreach(var obj in objList) 
    {
//convert the object to a Dictionary and select the 4th element.
      var yourresult = (obj as IDictionary<string, object>).ElementAt(3);
    }

Upvotes: 0

lasseeskildsen
lasseeskildsen

Reputation: 609

Another option is to deserialize to dynamic and inspect that:

var json = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";

var result = JsonConvert.DeserializeAnonymousType<dynamic>(json, null);

if (result.data != null)
{
    for (var i = 0; i < result.data.Count; i++)
    {
        if (result.data[i]["iValue"] != null)
            // Parse iValue
        if (result.data[i]["sValue"] != null)
            // Parse sValue

    }

}

Upvotes: 0

Ray Krungkaew
Ray Krungkaew

Reputation: 6977

  1. If you don't know the data type then you could use an object to handle it.
  2. It's a good idea to deserialize JSON string to concrete class to avoid string manipulation mistake.

    public class Datum
    {
        public object pKey { get; set; }
        public string Entity { get; set; }
        public string Attribute { get; set; }
        public string iValue { get; set; }
        public string sValue { get; set; }
    }
    
    public class DataCollection
    {
        public List<Datum> data { get; set; }
    }
    
    public void Test()
    {
        var str = "{\"data\":[{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"CID\",\"iValue\":\"13\"},{\"pKey\":\"0\",\"Entity\":\"tableName\",\"Attribute\":\"username\",\"sValue\":\"test_user1\"}]}";
        var list = JsonConvert.DeserializeObject<DataCollection>(str);
        var keys = list.data.Select(x => x.pKey).ToList();
    }
    

Upvotes: 0

Claus
Claus

Reputation: 1995

I would deserialize this into an object supporting both types of properties and then by code try parsing either the integer or the string if the integer fails.

If the Attribute value gives you a clue as to which one to look for, you could also use that to prevent having to try parsing the integer every time.

I would not rely on the property being the "fourth" property every time, as I'm assuming this would be external data, where you may not be able to control whether these properties come out in the exact same order every time (now and in the future).

Upvotes: 0

Related Questions