How to implement ForEach statement on Newtonsoft JArray - c#

I need to cast JArray which I received from browser to list of MyType object. The problem is that I don't want to use .ToObject<> extension because object sended from browser can have missing or extra values and I cannot change deserializer. I wrote my own parser to receive data from single JObject. Usage:
foreach (var _object in _jarray)
{
using (var tp = new TokenParser(_object))
{
MyType test = new MyType()
{
id = tp.ConvertToInt("token_key")
};
}
}
What I want to do is something like this
_jarray.ForEach(_object => {
using (var tp = new TokenParser(_object))
{
MyType test = new MyType()
{
id = tp.ConvertToInt("token_key")
};
}
});
I don't want to cast elements like:
_jarray.Select(x =>
(int)["token_key"]
);
I tried to wrote this extension based on LINQ equivalent of foreach for IEnumerable<T> but it seems that Action requires T as parameter type. I don't need this because JArray is an array of JObject.
Is this possible to achieve?

Related

Deserializing a JSON with nested dynamic property names

I'm trying to deserialize a pretty ugly JSON provided by an external REST API and am wondering about the "proper" way to do that (I'm using System.Text.Json in .net 6). Details follow:
I have a model for the data:
class DeviceData{
//lots of properties
}
which works fine (i.e I can just JsonSerializer.Deserialize<DeviceData> the response) when making an API query for a single instance, since it returns a nice JSON one would expect:
{
"property1_name": value,
"property2_name": value,
...
}
The problem begins when I use the batch query provided by the API, since the response to api_url/batch?=device1,device2,... looks as if someone failed to make an array (the device1s are alphanumeric strings pulled form a database) is:
{
"names":[
"device1",
"device2",
...
],
"device1":{
"stuff_i_dont_need": value,
"device1": {
"property1_name": value,
"property2_name": value,
...
}
}
"device2":{
...
}
...
}
The double nesting of dynamic property names means I can't just deserialize the second response as a dictionary of <string, myclass> pairs. I managed to hack something together using JsonDocument but it's extremly ugly and it feels like there should be a nice short way to do that with just JsonSerializer and maybe some reader overrides.
Using Deserialize subsections of a JSON payload from How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json as template you could do something like this:
JsonNode root = JsonNode.Parse(json)!;
Dictionary<string, X> devices = new();
foreach(string name in root["names"]!.AsArray()) {
var o = root[name][name].AsObject();
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
o.WriteTo(writer);
writer.Flush();
X? x = JsonSerializer.Deserialize<X>(stream.ToArray());
var innerJson = root[name][name].ToJsonString();
devices[name] = x;
}
foreach(var d in devices) Console.WriteLine($"{d.Key}: {d.Value}");
This prints
device1: X { property1_name = 12, property2_name = 13 }
device2: X { property1_name = 22, property2_name = 23 }
I'm not sure if this is faster/better than calling ToJsonString():
JsonNode root = JsonNode.Parse(json)!;
Dictionary<string, X> devices = new();
foreach(string name in root["names"]!.AsArray()) {
var innerJson = root[name][name].ToJsonString();
devices[name] = JsonSerializer.Deserialize<X>(innerJson);
}
foreach(var d in devices) Console.WriteLine($"{d.Key}: {d.Value}")
If you're after fancy you could go full LINQ:
JsonNode root = JsonNode.Parse(json)!;
Dictionary<string, X> devices = root["names"]!.AsArray()
.Select(name => (string)name)
.ToDictionary(
keySelector: name => name,
elementSelector: name => System.Text.Json.JsonSerializer.Deserialize<X>(root[name][name].ToJsonString()));
foreach(var d in devices) Console.WriteLine($"{d.Key}: {d.Value}");
Both print

Using Linq to convert KeyValue pairs into Newtonsoft.Json.Linq.JObject

I am trying to use C# LINQ to build a JObject. I know that I can use a loop, such as,
var jobj = new JObject();
foreach (var field in fieldList)
{
jobj[field.Name] = new JValue(field.Value);
}
Is it possible to replace the loop with LINQ? I tried this,
var data = fieldList.Select(field => new KeyValuePair<string, JValue>(field.Name, new JValue(field.Value)));
var jobj = new JObject(data);
but it fails with this error:
Could not determine JSON object type for type System.Collections.Generic.KeyValuePair`2[
System.String,Newtonsoft.Json.Linq.JValue].
Here
jobj[field.Name] = new JValue(field.Value);
you actually are calling the following JObject indexer:
public JToken this[string propertyName] { get; set; }
i.e. you are setting the JObject property.
So the LINQ equivalent will be like this:
var data = fieldList.Select(field => new JProperty(field.Name, field.Value));
var jobj = new JObject(data);
Well, I don't know if it's any prettier, but you could use Aggregate:
fieldList.Aggregate(new JObject(), (obj, next) => {obj[next.Name] = new JValue(next.Value); return obj;})
It would be nice if JObject had a chainable API, but it doesn't seem to.

Cannot retrieve the key and Value info from the deserialized data

dynamic data = JsonConvert.DeserializeObject(result);
foreach (var field in data)
{
var deserializedData = new DeserializedData
{
Label = field.Key.ToObject<string>(),
Content = field.Value.ToObject<string>()
};
}
Note: data = {"name":"test"}
Error:'Newtonsoft.Json.Linq.JProperty' does not contain a definition for 'Key'
I think I understand what the issue is but I am not sure if there is a way to achieve this behavior or is there a different approach to handle the dynamic key value scenarios like this.
The Key will change so I don't know if its going to be name or phone or email or some other field...
In order to iterate through the children of the returned data as an IEnumerable<KeyValuePair<string, JToken>>, you need to explicitly declare the deserialized object as a JObject:
var data = JObject.Parse(result);
foreach (var field in data)
{
var deserializedData = new DeserializedData
{
Label = field.Key,
Content = field.Value.ToObject<string>()
};
}
When you do dynamic data = JsonConvert.DeserializeObject(result);, the foreach ends up calling the IEnumerable<JToken> iterator in the base class JToken. I.e. the following also works:
dynamic data = JsonConvert.DeserializeObject(result);
foreach (var field in data)
{
// "field" happens to be of type JProperty
var deserializedData = new DeserializedData
{
Label = field.Name,
Content = field.Value.ToObject<string>()
};
}
Though I prefer the strongly typed version using JObject since more checking can be done at compile time.
According to the documentation the JProperty's value contains the key, and the JProperty's children are the values. source

Parsing complex JSON Object with C#

I have a JSON like this one:
{
"name" : "MyCustomName",
"my_node" : {
"name" : "my_node_name"
},
"dict1":"value1",
"dict2":"value2",
"dict3":"value3",
...
}
and an object:
class Node{
string value;
}
class Sample:IDictionary<string, string>{
Node node;
string name;
}
Node and Name in Sample class are always present.
The thing is I don't know how many "dictN" fields will be... and that's the point.
And the question is:
How to Deserialize that JSON to this Object?
Edit: apparently even with field names harmonized, your deserializer just can't cope with specific fields combined with general dictionary fields.
In which case, I'd just advise deserializing as a Dictionary<string, object> and building with the result:
var d = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
Sample s = new Sample();
s.name = (string)d["name"];
Node n = new Node();
n.value = (string)((Dictionary<string, object>)d["my_node"])["name"];
foreach (var key in d.Keys.Except(new string[] { "name", "my_node" }))
{
s.Add(key, (string)d[key]);
}
INITIAL IDEA
The following is a dictionary serializer. It has one special case of not accepting empty string.
private void SerializePerinatalModel<T>(IDictionary<string, object> dataModel, T perinatalModel)
{
Type sourceType = typeof(T);
foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
{
if (dataModel.ContainsKey(propInfo.Name))
{
// if an empty string has been returned don't change the value
if (dataModel[propInfo.Name].ToNullSafeString() != String.Empty)
{
try
{
Type localType = propInfo.PropertyType;
localType = Nullable.GetUnderlyingType(localType) ?? localType;
propInfo.SetValue(perinatalModel, Convert.ChangeType(dataModel[propInfo.Name], localType), null);
}
catch (Exception e)
{
// ToDo: log update value errors
}
}
}
}
}
but could be made null safe. It does deal with nullable types.
As JSON is essentially a dictionary type then iterating through the top level types should get you there.
This is written in haste so is only a sketch of an idea.
BETTER IDEA
Also try using
foreach (var item in JsonData.Where(m => m.Key.Substring(0,4) == "dict"))
{
// add item to collection
}
might also do the biz.
You can simply have the output in the form of Dictionary<string, object>, try this piece of code instead.
System.Web.Script.Serialization.JavaScriptSerializer s =
new System.Web.Script.Serialization.JavaScriptSerializer();
var nodes = s.Deserialize<Dictionary<string, object>>(jsonString);
var dictNodes = nodes.Where(w => w.Key.StartsWith("dict"));

Array of dynamic | ExpandoObject | with a compressed initialize syntax

Im trying to use DynamicObject in c#, and I needed an array of dynamic:
var d = new dynamic[];
which works fine.
EDIT : See ExpandoObject below.
But I also like to fill that array with some data with this compressed initialize new syntax:
var d = new dynamic[] {
new {
Name = "Some",
Number = 1010
},
new {
Name = "Other",
Number = 2010
}
}
But in that case all objects gets the non-dynamic type "object" and a loop through the items will give me an exception:
foreach (dynamic item in d)
{
#item.Name
#item.Number
}
Error : 'object' does not contain a definition for 'Name'. I guess I just initialize the array items the wrong way. How to add dynamic objects instead?
EDIT: New content:
I realize "dynamic" does not have the capability to dynamically add properties.
I better be using ExpandoObject which exposes all items in an an internal dictionary as properties. But unfortunately ExpandoObject does not seem to support this nice compressed create syntax, and the compiler complains:
var d = new ExpandoObject[]{
new ExpandoObject(){
Name="Nnn",
Number=1080
}
}
So the answer might just be : it's not possible.
Hopefully you do not really need dynamics
class Program
{
static void Main(string[] args)
{
var d = new[]
{
new
{
Name = "Some",
Number = 1010
},
new
{
Name = "Other",
Number = 2010
}
};
foreach (var item in d)
{
string s = #item.Name;
int n = #item.Number;
Console.WriteLine("{0} {1}", s, n);
}
}
}
I come a bit late but here is what i found about it :
if I can't initialise an ExpandoObject, how about initialising it with a dynamic type ?
so i did the following extension method
public static ExpandoObject CreateExpando(this object item)
{
var dictionary = new ExpandoObject() as IDictionary<string, object>;
foreach (var propertyInfo in item.GetType().GetProperties())
{
dictionary.Add(propertyInfo.Name, propertyInfo.GetValue(item, null));
}
return (ExpandoObject)dictionary;
}
I know it's far from ideal, but it's the best I could achieve for now, it works like this :
var myExpandoObject = new { Name="Alex", Age=30}.CreateExpando();
The open source framework Impromptu-Interface has a compressed initialization syntax that works with ExpandoObject.
dynamic #new = Builder.New<ExpandoObject>();
var d = #new.List(
#new.Expando(
Name:"Some",
Number: 1010
),
#new.Expando(
Name:"Other",
Number: 2010
)
);

Categories

Resources