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
Related
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
I am trying to create a JObject on the fly for unit testing but it does not seem to like name.subname as a property
The json that I am trying to create JObject for is
{ "UserDetails": {
"FirstName": "Test1",
"Surname": "Test2",
"UserID": "123456789",
"Children1.Active1": false,
"Children1.Active2": false
}
}
The C# code I have is
dynamic jsonObject = new JObject();
jsonObject.UserDetails = new JObject();
jsonObject.UserDetails.UserID = "123456789";
jsonObject.UserDetails.FirstName = "Test1";
jsonObject.UserDetails.Surname= "Test2";
However i am not sure how to add Children1.Active to the JObject jsonObject. UserDetails it does not work with jsonObject.UserDetails.Children.Active1 = false.
I have validated the json and it passes validation for being valid json.
How can I add Children1.Active to JObject?
I have tried jsonObject.UserDetails.Children1 = new JObject();
But this creates a new sub object which is not what I need.
"Children1.Active2" isn't a nested object, it's a property with a period in the name string. And since you're doing everything via dynamic, this becomes a nuisance, as c# identifiers can't include such a character even though JSON property names can.
In such cases, instead of using dynamic, it's easier to do things with a typed JObject and its compile-time-bound methods, specifically its string indexer method:
var jsonObject = new JObject();
var userDetails = new JObject();
jsonObject["UserDetails"] = userDetails;
userDetails["UserID"] = "123456789";
userDetails["FirstName"] = "Test1";
userDetails["Surname"] = "Test2";
userDetails["Children1.Active1"] = false;
userDetails["Children1.Active2"] = false;
This should also be more performant as dynamic can have some run-time performance penalties.
Sample fiddle here.
I am trying to create a Newtonsoft JObject with a custom DateFormatSting ("yyyy-MM-ddTHH:mm:ssZ") using JOjbect.FromObject and I think there is a bug. My code is:
JObject jBytes = JObject.FromObject(myObject, MyJsonSerializer);
Here, JObject.FromObject seems to ignore the DateFormatString in my custom JsonSerializer.
I have a workaround, but still curious if I am doing something wrong, or if anyone else has seen this?
(workaround)
JObject jBytes = Object.Parse(JsonConvert.SerializeObject(myObject, MyDateFormatString);
The reason you are seeing this is that JValue stores the DateTime as an actual DateTime structure copied from your object, not as a string. Therefore the DateFormatString is not applied during the conversion to JToken hierarchy. You can verify this by doing the following:
var types = jBytes.DescendantsAndSelf().OfType<JValue>().Where(v => v.Type == JTokenType.Date).Select(v => v.Value.GetType().FullName);
Debug.WriteLine(JsonConvert.SerializeObject(types, Formatting.None));
The output will be ["System.DateTime", ...].
Thus the setting needs to be applied when you convert your JToken to its ultimate JSON string representation. Unfortunately, there doesn't seem to be a convenient ToString() overload on JToken allowing a DateFormatString to be specified. One possibility would be to serialize the root token:
var settings = new JsonSerializerSettings { DateFormatString = "yyyy/MM/dd" };
var jBytes = JObject.FromObject(myObject);
var json = JsonConvert.SerializeObject(jBytes, Formatting.Indented, settings); // Outputs "2015/09/24"
This at least avoids the parsing overhead of JToken.Parse() in your workaround.
Another option would be introduce an extension method modeled on JToken.ToString() and the TraceJsonWriter constructor that takes a JsonSerializerSettings and applies the appropriate settings:
public static class JsonExtensions
{
public static string ToString(this JToken token, Formatting formatting = Formatting.Indented, JsonSerializerSettings settings = null)
{
using (var sw = new StringWriter(CultureInfo.InvariantCulture))
{
using (var jsonWriter = new JsonTextWriter(sw))
{
jsonWriter.Formatting = formatting;
jsonWriter.Culture = CultureInfo.InvariantCulture;
if (settings != null)
{
jsonWriter.DateFormatHandling = settings.DateFormatHandling;
jsonWriter.DateFormatString = settings.DateFormatString;
jsonWriter.DateTimeZoneHandling = settings.DateTimeZoneHandling;
jsonWriter.FloatFormatHandling = settings.FloatFormatHandling;
jsonWriter.StringEscapeHandling = settings.StringEscapeHandling;
}
token.WriteTo(jsonWriter);
}
return sw.ToString();
}
}
}
Then you can just do:
var settings = new JsonSerializerSettings { DateFormatString = "yyyy/MM/dd" };
var json = jBytes.ToString(Formatting.Indented, settings); // Outputs "2015/09/24"
Prototype fiddle.
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);
...
}
I just got a hold of JSON.NET and its been great so far.
However, I cannot figure out how to determine the type of a serialized object when deserializing it.
How can I determine the object's class to cast it?
To clarify my question, let's say I wanted to do this
string json = <<some json i don't know>>
var data = JsonConvert.DeserializeObject(json);
if (data is Person)
{
//do something
}
else if (data is Order)
{
//do something else
}
Does Json.NET support this kind of functionality?
you can use dynamic type
JsonConvert.DeserializeObject<dynamic>(JSONtext)
it may help you
IDictionary < string, JToken > Jsondata = JObject.Parse(yourJsonString);
foreach(KeyValuePair < string, JToken > element in Jsondata)
{
string innerKey = element.Key;
if (element.Value is JArray)
{
// Process JArray
}
else if (element.Value is JObject)
{
// Process JObject
}
}
In case you control the serialization, you can use the TypeNameHandling setting
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var toBeSerialized = settings; // use the settings as an example data to be serialized
var serialized = JsonConvert.SerializeObject(toBeSerialized, Formatting.Indented, settings);
var deserialized = JsonConvert.DeserializeObject(serialized, settings);
var deserializedType = deserialized.GetType().Name; // JsonSerializerSettings
for anyone still trying to do this, I suggest to use
JsonConvert.DeserializeObject<ExpandoObject>(JSONtext)