JToken type conversion? - c#

I have a JObject containing parameter/value pairs (e.g. "param1": "aValue"). I want to update these values from data captured in a data grid.
The parameter values are then validated against a JSON schema that defines the following types: "string", "integer" and "array" (of type "string").
My code works fine for as long as the parameter values are of type string. For integers and arrays I get the following error:
// parameter validation code
..
IList<string> messages;
bool result = Params.IsValid(paramSchema, out messages);
..
// "Validation error: Invalid type. Expected Integer but got String"
// I get a corresponding error for type "Array"
Fair enough, I then added a switch to get the original type so I can cast the new value accordingly:
..
foreach (DataGridViewRow row in Param.Rows)
{
// get current value
JToken curToken = aJObject.GetValue(aDatagrid.row.Cells[0].Value.ToString());
// get new value
JToken newToken = aDatagrid.row.Cells[1].Value.ToString();
switch (currToken.Type)
{
case JTokenType.Integer:
newToken = convertTokenToInt(newToken); // ==> works but custom method
break;
case JTokenType.Array:
newToken = (JTokenType.Array)newToken; // ==> casting does not work!
break;
}
currToken.Replace(newToken);
}
..
With this code I can now handle "strings" and "integers" but I need another custom method for "arrays" (of type "string") or any other type I might use in the future. Not happy with this...
My custom convertTokenToInt(newToken) routine simply assigns the value to an int then reassigns it back to the JToken.
..
int number;
bool result = int.TryParse(aToken.ToString(), out number);
if (result)
{
aToken = number;
return aToken;
}
..
This seems to suggest implicit type conversion is possible with JToken's, so why can't I use casting or ConvertTo() or some other more conventional way to map a datagrid cell value to the appropriate JToken type?
Appreciate any help to resolve this simple problem :-)

... here is my conversion method that works, but maybe there is a more elegant solution ...
private JToken convertTokenToArray(JToken aToken)
{
JArray arr = new JArray();
List<string> items = JsonConvert.DeserializeObject<List<string>>(aToken.ToString());
foreach (var item in items)
{
JToken i = item;
arr.Add(i);
}
return arr;
}

Related

How to find out what type a JsonValue is in System.Text.Json

So when I have a JsonNode I can just ask if it's a JsonObject or a JsonArray and work with those. But when the node is an actual value, how do I know whether it's a string, number or boolean?
Of course I could just try and parse the value, but then a number transmitted in a string would become a number instead of a string which I'd like to avoid.
I'm using System.Text.Json with .NET 6.
From the source, it looks like a JsonValue just wraps a JsonElement. So you can do .GetValue<JsonElement>() (which passes this check), and then inspect its ValueKind property.
The accepted answer only works for specific use cases, namely that the node in question is of type JsonValue.
I propose a better option is to first test for the basic kind of types there are in JSON.
someObject["SomeNode"] is JsonArray
someObject["SomeNode"] is JsonObject
someObject["SomeNode"] is JsonValue
if the object is of type JsonValue one can use tryGetvalue to directly test for the expected value type
someObject["SomeNode"].AsValue().TryGetValue<someType>(out someType result)
TryGetValue returns true if it the value was parseble as the requested type.
If the expected type is completely unknown or variable (ugh), you could use the
someObject["SomeNode"].GetValue<JsonElement>().ValueKind
trick. But that only works for distinquishing between int and string and the bool values. Trying this on an array will give an exception. Therefore you first have to test with the "is" style syntax above.
Each JsonProperty has two properties - Name and Value of Type JsonElement. JsonElement has an Enum property named ValueKind which can help you determine what of what data type your JSON value is.
You can get JsonProperties by calling .EnumerateObject() on your JsonElement. You can work with your Json document as a JsonElement instead of JsonObject.
The following code works in .NET fiddle with .NET 6.
Note:
if your JsonValue ultimately came from a JsonNode.Parse*(...), then your JsonValue will contain a "JSON type".
if your JsonValue was created using JsonValue.Create() or an implicit conversion, then your JsonValue will contain a "CLR type".
The following code returns an approximate "CLR type" for "JSON types", because of the next point.
JsonValue.GetValue<T>() only does type conversion if JsonValue contains a "JSON type".
JsonValue.GetValue<object>() conveniently returns the underlying value, which is a JsonElement if it is a "JSON type".
public static class JsonValueExtensions
{
public static Type GetValueType(this JsonValue jsonValue)
{
var value = jsonValue.GetValue<object>();
if (value is JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.False => typeof(bool),
JsonValueKind.True => typeof(bool),
JsonValueKind.Number => typeof(double),
JsonValueKind.String => typeof(string),
var _ => typeof(JsonElement),
};
}
return value.GetType();
}
}
Depending on the type of JsonElement returned you have to handle it differently.
My case was that the returned element was ValueKind = Array : "[[47.751]]"
So in order to get it I did created this method
private object GetValueFromJsonElement(WorkbookRange range)
{
var element = range.Values.RootElement.EnumerateArray().First()[0];
switch (element.ValueKind)
{
case JsonValueKind.Number:
return element.GetDouble();
case JsonValueKind.String:
return element.GetString();
case JsonValueKind.True:
case JsonValueKind.False:
return element.GetBoolean();
default:
throw new InvalidOperationException("The Value Type returned is not handled");
}
}

How can I set a List<foo> field of an object with the value of List<dynamic>

I have been trying to set some field values of specific objects in C#.
For other reasons I need to construct a List of values from a string then I want to assign this to a field in an object.
The way I indicate the value type of the list is something like this in string
name:type{listItemName:listItemType{listItemValue}}
This list can be of any type, so it is undetermined until we reach conversion.
I am using
List<dynamic> ldy = new List<dynamic>();
foreach (string listElement in listElements)
{
if (listElement == "") continue;
Type leDataType = GetDataType(listElement);
string leData = GetDataRaw(listElement);
var leDynamic = ConstructDataObject(leDataType, leData, listElement);
ldy.Add(leDynamic);
}
Which ends up with the correct data and with the correct data type when I enquire, however when I am trying to use the resulting ldy list and assign it to a field, of course it acts as a List<object>
thus not allowing me to assign.
Finally so, I am using field.SetValue(targetObject, ldy); to assign the value to the field.
The error message I am getting is
ArgumentException: Object of type 'System.Collections.Generic.List`1[System.Object]' cannot be converted to type 'System.Collections.Generic.List`1[System.Int32]'.
Which to be clear, I do understand and I do understand why, however I dont really see how could I solve this issue, not by solving this nor by changing the fundaments of my code design.
Please help!
As #juharr suggested I have searched for solutions to do this with reflection.
Here is my solution:
private static IList GetTypedList(Type t)
{
var listType = typeof(List<>);
var constructedListType = listType.MakeGenericType(t);
var instance = Activator.CreateInstance(constructedListType);
return (IList)instance;
}

C# convert comma separated string to dynamic type

How do I convert a CSV string to an array of a type that is only known at run time. Say I have 2 array fields in my class of different types declared as int[] intArray and string[] strArray. I want a single function where I pass in 2 parameters the field name and CSV string. I can use the ...; FieldInfo f =... ; Type t = f.FieldType.GetElementType(); But what I can't do is declare List<t> because t is a variable and not a type. I saw one post suggesting csv.Split(',').Select(s => Convert.ChangeType(s, t)).ToArray() but this comes out as an object array not an int or string array; another post saying ...; var list = (IList) Activate.CreatInstance(...), which is fine that I can call list.Add(...) but then what do I do as I need an Array of t.
I'd recommend using a nuget package to do this parsing. CsvHelper has an example of how to deserialize to a given type.
I have bitten the bullet on this one and so will have to update my code if I need a new type like array of float numbers say is required but this suffices for what I need today, and I do agree with the other mentions about CSV what if my values had commas say addresses and came in as "\"12 George St, Sydney\",\"200 Sussex St, Sydney\"" then I would have to consider advanced CSV however again this is all I need for today.
class SetPropertyByName
{
public object this[string fieldName]
{
set
{
FieldInfo field = this.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
if (null != field)
{
if (field.FieldType.IsArray && value is String)
{
string newValue = (string)value;
if (string.IsNullOrEmpty(newValue))
value = null;
else
{
var conversionType = field.FieldType.GetElementType();
if (conversionType == typeof(string))
{
value = newValue.Split(',').ToArray();
}
else if (conversionType == typeof(int))
{
value = newValue.Split(',').Select(s => Convert.ToInt32(s)).ToArray();
}
}
field.SetValue(this, value);
}
else
field.SetValue(this, Convert.ChangeType(value, field.FieldType));
}
}
}
}

javascript custom converter deserialize json array

I have an object model that includes an array of longs and I'm deserializing a json string that contains an array using a custom javascript converter and the javascript serializer class.
I thought this would work but it doesn't:
List<long> TheList = new List<long>;
if (dictionary.ContainsKey("TheArray") && dictionary["TheArray"] != null)
{
TheList = serializer.ConvertToType<List<long>>(dictionary["TheArray"]); //bug
TheObject.TheObjectList = (from s in TheList
select Convert.ToInt64(s)).ToList<long>();
}
The error is on the line TheList = serializer.ConvertToType... and the error message is:
Cannot convert object of type 'System.String' to type
'System.Collections.Generic.List`1[System.Int64]'
I also tried this:
var TheStringArray = serializer.ConvertToType<string>(dictionary["TheArray"]);
TheObject.TheObjectList = (from s in TheStringArray.Split(',')
select Convert.ToInt64(s)).ToList<long>();
But then I get this error message:
Type 'System.String' is not supported for deserialization of an array.
What am I missing?
Thanks.
Array is visible to JavaScriptConverter as ArrayList, you may approach the deserialization like this:
List<long> theArray = null;
if (dictionary.ContainsKey("TheArray") && dictionary["TheArray"] is ArrayList)
{
theArray = new List<long>();
ArrayList serializedTheArray = (ArrayList)dictionary["TheArray"];
foreach (object serializedTheArrayItem in serializedTheArray)
{
if (serializedTheArrayItem is Int64)
theArray.Add((long)serializedTheArrayItem);
}
}
This will do all types checking in case there is somethign unexpected in the JSON. Of course it assumes that TheArray property in JSON in fact contains an Array, not inner JSON string which represents Array (the error message might suggest this kind of issue).

Get an enumerated field from a string

Bit of a strange one this. Please forgive the semi-pseudo code below. I have a list of enumerated values. Let's say for instance, like so:
public enum Types
{
foo = 1,
bar = 2,
baz = 3
}
Which would become, respectfully, in the code:
Types.foo
Types.bar
Types.baz
Now I have a drop down list that contains the following List Items:
var li1 = new ListItem() { Key = "foo" Value = "Actual Representation of Foo" }
var li2 = new ListItem() { Key = "bar" Value = "Actual Representation of Bar" }
var li3 = new ListItem() { Key = "baz" Value = "Actual Representation of Baz" }
for the sake of completeness:
dropDownListId.Items.Add(li1); dropDownListId.Items.Add(li2); dropDownListId.Items.Add(li3);
Hope that everyone is still with me. What I want to do is to on the Autopostback is take the string "foo" and convert that to Types.foo - without using a switch (as the enumerated values are generated from a database and may change).
I hope that makes sense? Any idea where to even start?
Sure:
Types t;
if(Enum.TryParse(yourString, out t)) // yourString is "foo", for example
{
// use t
}
else
{
// yourString does not contain a valid Types value
}
There's also an overload that takes a boolean that allows you to specify case insensitiveness:
http://msdn.microsoft.com/en-us/library/dd991317.aspx
Enum.TryParse is new in .NET 4. If you're stuck on a previous version, you'll have to use the non-typesafe Enum.Parse method (which throws an exception in case of conversion failure, instead of returning false), like so:
try
{
Types t = (Types)Enum.Parse(typeof(Types), yourString);
// use t
}
catch(ArgumentException)
{
// yourString does not contain a valid Types value
}
Enum.Parse also has an overload for case insensitiveness.
So, you want: Enum.Parse(typeof(Types), postbackValue)
or did I miss something?
If I understood correctly, you can do:
Types fooEnum = Enum.Parse(typeof(Types), "foo");
See: http://msdn.microsoft.com/en-us/library/essfb559.aspx

Categories