Serialize data to json string with dynamic property names - c#

I have a method which accepts a key and a value. Both variables can have a dynamic content.
key => is a dynamic string which can be everything like e.g. "LastSentDate"
value => is an object which can be everything like e.g. "2014-10-10"
As key is a dynamic value like "LastSentDate" or whatever key is passed to the method then I want that the json property is the value of the key string and not literally key itself...
public void SetRowVariable(string key, object value)
{
var obj = new { key = value }; // key property is literally taken maybe anonym object is not a good idea?
string jsonString = JsonConvert.SerializeObject(obj);
// jsonString should have that output => "{ "LastSentDate": "2014-10-10" }"
}
How do I have to serialize the obj that I get the wished output?
It must also be possible that the "key" property can contain special chars like "!"§$%&/()=?"`
I am using .NET 3.5 sadly.

You could use a JObject (in Newtonsoft.Json.Linq):
var obj = new JObject();
obj[key] = JToken.FromObject(value);
string jsonString = obj.ToString();

You may try using a Dictionary<string, object>:
public void SetRowVariable(string key, object value)
{
var obj = new Dictionary<string, object>();
obj[key] = value; // Of course you can put whatever crap you want here as long as your keys are unique
string jsonString = JsonConvert.SerializeObject(obj);
...
}

Related

Render scriban template from json data

I would like to render a Scriban template with an ExpandoObject or any other data type that can be generated from a JSON string:
var json = "....";
var dyn = JsonConvert.DeserializeObject<ExpandoObject>(json);
var template = Scriban.Template.Parse("Hello {{ data.foo }}!");
var result = template.Render(dyn);
Scriban does not work with ExpandoObjects, as they are parsed as a list of { key = '', value = '' } objects. Declaring a type for the data is not an option in my use case as the JSON schema is not known a priori.
Casting ExpandoObject to dynamic shows the same behavior as using ExpandoObject directly.
I've tried deserializing the JSON to dynamic which leads to an exception:
System.Reflection.TargetParameterCountException: "Parameter count mismatch."
Can I somehow convert the data or configure Scriban to render dynamic data?
Based on the previous answer this is a solution for complex objects:
public static class ScribanRenderer
{
public static string RenderJson(string json, string content)
{
var expando = JsonConvert.DeserializeObject<ExpandoObject>(json);
var sObject = BuildScriptObject(expando);
var templateCtx = new Scriban.TemplateContext();
templateCtx.PushGlobal(sObject);
var template = Scriban.Template.Parse(content);
var result = template.Render(templateCtx);
return result;
}
private static ScriptObject BuildScriptObject(ExpandoObject expando)
{
var dict = (IDictionary<string, object>) expando;
var scriptObject = new ScriptObject();
foreach (var kv in dict)
{
var renamedKey = StandardMemberRenamer.Rename(kv.Key);
if (kv.Value is ExpandoObject expandoValue)
{
scriptObject.Add(renamedKey, BuildScriptObject(expandoValue));
}
else
{
scriptObject.Add(renamedKey, kv.Value);
}
}
return scriptObject;
}
}
It leverages the fact that complex properties of an ExpandoObject are always ExpandoObjects when it gets deserialized from JSON. It recursively adds ScriptObjects for complex member types and the object itself for all other properties to the ``ScriptObject`.
Please not that my solution uses Scriban's default member renaming, where FirstName becomes first_name and so on.
In cases when you have a simple object you can try something like this.
var json = "{\"Phone\":\"555000000\",\"Name\":\"Maia\"}";
var scriptObject = new Scriban.Runtime.ScriptObject();
var data = JsonConvert.DeserializeObject<ExpandoObject>(json);
foreach (var prop in data)
{
scriptObject.Add(prop.Key, prop.Value);
}
var templateCtx = new Scriban.TemplateContext();
templateCtx.PushGlobal(scriptObject);
var template = Scriban.Template.Parse("Hello {{Name}}");
var result = template.Render(templateCtx); // result will be "Hello Maia"
Not sure if the previous versions supported doing it, but it looks like this is more efficient, and the Scriban example (github) does this.
var json = "{\"name\":{\"first\":\"Maia\"}}";
var template = Template.Parse("Hello {{model.name.first}}");
var result = template.Render(new { model = JsonConvert.DeserializeObject<ExpandoObject>(json) });
Console.WriteLine(result);
// Output: Hello Maia

How to use an argument with a dynamic object?

Is it possible to pass in an argument to a dynamic object, so it returns the object required at runtime?
var file = File.ReadAllText("file.json");
dynamic json = JsonConvert.DeserializeObject(file);
var value = GetValue("EnvironmentConfiguration.Environment");
private static string GetValue(string jsonKey) {
// pass jsonKey somehow here? Something like this...
return json.(jsonKey);
}
Is this even possible?
EDIT:
Json file:
{
"EnvironmentConfiguration":
{
"Environment": "local"
}
}
Assuming we want to get the value "local" from "EnvironmentConfiguration.Environment".
I would use JObject and the SelectToken method:
var jobject = JObject.Parse(file);
var value = jobject
.SelectToken("EnvironmentConfiguration.Environment")
.ToString();
You can get your property by using indexer:
return json[jsonKey];
You can use Json.NET as follows:
dynamic json = JObject.Parse("{number:5555, str:'myString', array: [1,2,3,4,5]}");
var value1 = json.number; // get number value -> 5555
var value2 = json.str; // get string value -> myString
var value3 = json.array.Count; // get array count value -> 5

Changing Key Of An Property in Json

I'm trying to make a json editor(works with treeview) these days, i did changing value function, i can change some keys as well, but i cant set keys in objects.
I can set the value:
SetValue(ref JObject main,JToken token,JToken newValue) {
//2nd argument is obj.SelectToken(node.Path)
token.Replace(newValue);
}
I can also set some keys:
SetKey(ref JObject main,JToken token,string newKey) {
//2nd argument is obj.SelectToken(node.Path)
//However, if token is in object, it seys the key of object because parent is object
(token.Parent as JProperty).Replace(newKey);
}
But how can i set the keys?
Regards.
You don't need to pass the original root object by ref and you don't need the original root at all. All you care about is the JToken and its parent.
In this case, you want to think of "replacement" as:
Add the old value by new key
Remove the old key/value pair
public void SetKey(JObject parent, JToken token, string newKey)
{
var tokenProp = token as JProperty;
var oldKeyName = tokenProp.Name;
parent[newKey] = tokenProp.Value;
parent.Remove(oldKeyName);
}
We can assume that if you are replacing a key for a key value pair, that the object is a JProperty token. In addition, if we are replacing keys, it is also safe to assume the parent is a JObject. You can call it as such:
var json = "{ 'key1': 'val1' }";
JObject parsedObj = JsonConvert.DeserializeObject<JObject>(json);
SetKey(parsedObj, parsedObj.First, "key2");
I had a similar issue where I had to remove whitespaces from all properties within a JObject, but didn't want to use a helper function. With using System.Linq:
var descendants = jObject.Descendants().Where(attr => attr is JProperty && ((JProperty)attr).Name.Contains(" ")).ToList();
descendants.ForEach(attr => attr.Parent[((JProperty)attr).Name.Replace(" ", string.Empty)] = ((JProperty)attr).Value);
descendants.ForEach(attr => attr.Remove());

After deserialization cannot cast object

I am serializing and then deserializing a Dictionary in Newtonsoft.Json, however the output is not what I expected and is throwing exceptions. Can someone let me know what I'm doing wrong?
Here is my code
using Newtonsoft.Json;
namespace Task1
{
public class Class1
{
public static void Main()
{
var dict = new Dictionary<string, object>();
dict["int"] = new GenericItem<int> {CreatedAt = DateTime.Now, Object = 111};
dict["string"] = new GenericItem<string> {CreatedAt = DateTime.Now.Date, Object = "test test"};
var json = JsonConvert.SerializeObject(dict);
var desDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var test = dict["int"] is GenericItem<int>;
var test2 = dict["int"] as GenericItem<int>;
var test3 = (GenericItem<int>) dict["int"];
var desTest = desDict["int"] is GenericItem<int>;
var desTest2 = desDict["int"] as GenericItem<int>;
var desTest3 = (GenericItem<int>) desDict["int"];
}
}
public class GenericItem<T>
{
public DateTime CreatedAt { get; set; }
public T Object { get; set; }
}
}
First three tests return True, Instance of GenericItem<Int> and again Instance of GenericItem<Int>.
But after deserialization return false, null, and InvalidCastException.
What is the problem? Why after deserealization does it throw InvalidCastException?
What is the problem? Why after deserealization does it throw InvalidCastException?
The reason of this behaviour is simple - if you check the type of this element with desDict["int"].GetType(), you will see that it returns Newtonsoft.Json.Linq.JObject and that's definitely not the type you were expecting.
It is possible to do what you want with usage of TypeNameHandling parameter of JsonSerializerSettings class, as suggested in this SO answer. Change this:
var json = JsonConvert.SerializeObject(dict);
var desDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
to this:
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
var json = JsonConvert.SerializeObject(dict, settings);
var desDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json, settings);
Info from reffered answer:
The TypeNameHandling flag will add a $type property to the JSON, which
allows Json.NET to know which concrete type it needs to deserialize
the object into. This allows you to deserialize an object while still
fulfilling an interface or abstract base class.
The downside, however, is that this is very Json.NET-specific. The
$type will be a fully-qualified type, so if you're serializing it with
type info,, the deserializer needs to be able to understand it as
well.
As an alternative you can loop throught the deserialized dictionary to convert values for each key from JObject to desired type:
var desDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
var newDict = new Dictionary<string, object>(); //as you can't modify collection in foreach loop and the original dictionary can be out of scope of deserialization code
foreach (var key in desDict.Keys)
{
switch(key)
{
case "int":
newDict[key] = ((JObject)desDict[key]).ToObject<GenericItem<int>>();
break;
case "string":
newDict[key] = ((JObject)desDict[key]).ToObject<GenericItem<string>>();
break;
}
}
desDict = newDict;
You don't need that manual solution here, but maybe someday this knowledge will be useful. I found info about JOobject.ToObject() method in this SO answer. I've checked both methods and both work fine with the code you provided.
You ask it to deserialize to a Dictionary<string, object> so it does so. It doesn't store the type information so it builds an object to store the deserialized information in.
I don't think you can get what you want, due to variance issues. You could deserialize to a Dictionary<string, GenericItem<object>> but that doesn't seem to be what you wanted. You get a json.net specific object in place of your int or string. You could cast that to what you want.
try :
var desDict = JsonConvert.DeserializeObject<Dictionary<string, GenericItem<object>>>(json);
or :
var desDict = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(json);
using dynamic you don't need to cast your object at all, you can just use it's properties

Get value of c# dynamic property via string

I'd like to access the value of a dynamic c# property with a string:
dynamic d = new { value1 = "some", value2 = "random", value3 = "value" };
How can I get the value of d.value2 ("random") if I only have "value2" as a string? In javascript, I could do d["value2"] to access the value ("random"), but I'm not sure how to do this with c# and reflection. The closest I've come is this:
d.GetType().GetProperty("value2") ... but I don't know how to get the actual value from that.
As always, thanks for your help!
Once you have your PropertyInfo (from GetProperty), you need to call GetValue and pass in the instance that you want to get the value from. In your case:
d.GetType().GetProperty("value2").GetValue(d, null);
public static object GetProperty(object target, string name)
{
var site = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, name, target.GetType(), new[]{Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0,null)}));
return site.Target(site, target);
}
Add reference to Microsoft.CSharp. Works also for dynamic types and private properties and fields.
Edit: While this approach works, there is almost 20× faster method from the Microsoft.VisualBasic.dll assembly:
public static object GetProperty(object target, string name)
{
return Microsoft.VisualBasic.CompilerServices.Versioned.CallByName(target, name, CallType.Get);
}
Dynamitey is an open source .net std library, that let's you call it like the dynamic keyword, but using the a string for the property name rather than the compiler doing it for you, and it ends up being equal to reflection speedwise (which is not nearly as fast as using the dynamic keyword, but this is due to the extra overhead of caching dynamically, where the compiler caches statically).
Dynamic.InvokeGet(d,"value2");
The easiest method for obtaining both a setter and a getter for a property which works for any type including dynamic and ExpandoObject is to use FastMember which also happens to be the fastest method around (it uses Emit).
You can either get a TypeAccessor based on a given type or an ObjectAccessor based of an instance of a given type.
Example:
var staticData = new Test { Id = 1, Name = "France" };
var objAccessor = ObjectAccessor.Create(staticData);
objAccessor["Id"].Should().Be(1);
objAccessor["Name"].Should().Be("France");
var anonymous = new { Id = 2, Name = "Hilton" };
objAccessor = ObjectAccessor.Create(anonymous);
objAccessor["Id"].Should().Be(2);
objAccessor["Name"].Should().Be("Hilton");
dynamic expando = new ExpandoObject();
expando.Id = 3;
expando.Name = "Monica";
objAccessor = ObjectAccessor.Create(expando);
objAccessor["Id"].Should().Be(3);
objAccessor["Name"].Should().Be("Monica");
var typeAccessor = TypeAccessor.Create(staticData.GetType());
typeAccessor[staticData, "Id"].Should().Be(1);
typeAccessor[staticData, "Name"].Should().Be("France");
typeAccessor = TypeAccessor.Create(anonymous.GetType());
typeAccessor[anonymous, "Id"].Should().Be(2);
typeAccessor[anonymous, "Name"].Should().Be("Hilton");
typeAccessor = TypeAccessor.Create(expando.GetType());
((int)typeAccessor[expando, "Id"]).Should().Be(3);
((string)typeAccessor[expando, "Name"]).Should().Be("Monica");
Much of the time when you ask for a dynamic object, you get an ExpandoObject (not in the question's anonymous-but-statically-typed example above, but you mention JavaScript and my chosen JSON parser JsonFx, for one, generates ExpandoObjects).
If your dynamic is in fact an ExpandoObject, you can avoid reflection by casting it to IDictionary, as described at http://msdn.microsoft.com/en-gb/library/system.dynamic.expandoobject.aspx.
Once you've cast to IDictionary, you have access to useful methods like .Item and .ContainsKey
The GetProperty/GetValue does not work for Json data, it always generate a null exception, however, you may try this approach:
Serialize your object using JsonConvert:
var z = Newtonsoft.Json.JsonConvert.DeserializeObject(Convert.ToString(request));
Then access it directly casting it back to string:
var pn = (string)z["DynamicFieldName"];
It may work straight applying the Convert.ToString(request)["DynamicFieldName"], however I haven't tested.
d.GetType().GetProperty("value2")
returns a PropertyInfo object.
So then do
propertyInfo.GetValue(d)
To get properties from dynamic doc
when .GetType() returns null, try this:
var keyValuePairs = ((System.Collections.Generic.IDictionary<string, object>)doc);
var val = keyValuePairs["propertyName"].ToObject<YourModel>;
This is the way i ve got the value of a property value of a dinamic:
public dynamic Post(dynamic value)
{
try
{
if (value != null)
{
var valorCampos = "";
foreach (Newtonsoft.Json.Linq.JProperty item in value)
{
if (item.Name == "valorCampo")//property name
valorCampos = item.Value.ToString();
}
}
}
catch (Exception ex)
{
}
}
Some of the solutions were not working with a valuekind object that I obtained from a json string, maybe because I did not have a concrete type in my code that was similar to the object that I would obtain from the json string, so how I went about it was
JsonElement myObject = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(jsonStringRepresentationOfMyObject);
/*In this case I know that there is a property with
the name Code, otherwise use TryGetProperty. This will
still return a JsonElement*/
JsonElement propertyCode = myObject.GetProperty("Code");
/*Now with the JsonElement that represents the property,
you can use several methods to retrieve the actual value,
in this case I know that the value in the property is a string,
so I use the GetString method on the object. If I knew the value
was a double, then I would use the GetDouble() method on the object*/
string code = propertyCode.GetString();
That worked for me
In .Net core 3.1 you can try like this
d?.value2 , d?.value3
Similar to the accepted answer, you can also try GetField instead of GetProperty.
d.GetType().GetField("value2").GetValue(d);
Depending on how the actual Type was implemented, this may work when GetProperty() doesn't and can even be faster.
In case you have a dynamic variable such as a DapperRow for example you can first build up an ExpandoObject, then cast the Expando into an IDictionary<string, object>. From then on, getting a value via the name of a property is possible.
Helper method ToExpandoObject:
public static ExpandoObject ToExpandoObject(object value)
{
IDictionary<string, object> dapperRowProperties = value as IDictionary<string, object>;
IDictionary<string, object> expando = new ExpandoObject();
if (dapperRowProperties == null)
{
return expando as ExpandoObject;
}
foreach (KeyValuePair<string, object> property in dapperRowProperties)
{
if (!expando.ContainsKey(property.Key))
{
expando.Add(property.Key, property.Value);
}
else
{
//prefix the colliding key with a random guid suffixed
expando.Add(property.Key + Guid.NewGuid().ToString("N"), property.Value);
}
}
return expando as ExpandoObject;
}
Sample usage, I have marked in bold the casting which gives us access in the example, I have marked the important bits with the ** letters:
using (var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
foreach (var dynamicParametersForItem in dynamicParametersForItems)
{
var idsAfterInsertion = (await connection.QueryAsync<object>(sql, dynamicParametersForItem)).ToList();
if (idsAfterInsertion != null && idsAfterInsertion.Any())
{
**var idAfterInsertionDict = (IDictionary<string, object>) ToExpandoObject(idsAfterInsertion.First());**
string firstColumnKey = columnKeys.Select(c => c.Key).First();
**object idAfterInsertionValue = idAfterInsertionDict[firstColumnKey];**
addedIds.Add(idAfterInsertionValue); //we do not support compound keys, only items with one key column. Perhaps later versions will return multiple ids per inserted row for compound keys, this must be tested.
}
}
}
In my example, I look up a property value inside a dynamic object DapperRow and first convert the Dapper row into an ExpandoObject and cast it into a dictionary property bag as shown and mentioned in other answers here.
My sample code is the InsertMany method for Dapper extension I am working on, I wanted to grab hold of the multiple ids here after the batch insert.
Use dynamic with Newtonsoft.Json.JsonConvert.DeserializeObject:
// Get JSON string of object
var obj = new { value1 = "some", value2 = "random", value3 = "value" };
var jsonString = JsonConvert.SerializeObject(obj);
// Use dynamic with JsonConvert.DeserializeObject
dynamic d = JsonConvert.DeserializeObject(jsonString);
// output = "some"
Console.WriteLine(d["value1"]);
Sample:
https://dotnetfiddle.net/XGBLU1

Categories

Resources