I was been working with an extension method using generics and Newtonsoft.Json's JsonConvert.DeserializeObject<TSource>(jsonString)
the serialization works as expected
string value = string.Empty;
value = JsonConvert.SerializeObject(null); //"null"
value = JsonConvert.SerializeObject("null"); //"null"
value = JsonConvert.SerializeObject("value"); //"value"
value = JsonConvert.SerializeObject(""); //""
But when trying to Deserialize
string result = string.Empty;
result = JsonConvert.DeserializeObject("null"); //null, ok
result = JsonConvert.DeserializeObject("value"); //throwing error, expecting "value"
result = JsonConvert.DeserializeObject(""); //throwing error, expecting string.empty
Error: Unexpected character encountered while parsing value: v. Path '', line 0, position 0.
now I'm used where TSource : new () on the extension method, so that any string return types would be restricted as
public static TSource ExecuteScriptForData(this IJavaScriptExecutor javaScriptExecutor, string script, params object[] args) where TSource : new ()
which is not letting me to use interfaces like IList, IPerson or ReadOnlyCollection on TSource
Now Is there any way to configure the Deserializer so that it would be able to Deserialize strings as Serializer is producing ?
Now Is there any way to configure the Deserializer so that it would be able to deserialize strings as Serializer is producing ?
You can use JsonSerializerSettings's TypeNameHandling property.
var settings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };
var str = JsonConvert.SerializeObject(null,settings);
var obj1 = JsonConvert.DeserializeObject(str, settings);
str = JsonConvert.SerializeObject("value", settings);
var obj2 = JsonConvert.DeserializeObject(str, settings);
str = JsonConvert.SerializeObject("", settings);
var obj3 = JsonConvert.DeserializeObject(str, settings);
value in json does not have any meanings. If you expect your result to be the value of string you have to put it in qoutes:
string result = string.Empty;
result = JsonConvert.DeserializeObject<string>("null"); //null, ok
result = JsonConvert.DeserializeObject<string>("'value'"); // "value"
result = JsonConvert.DeserializeObject<string>("''"); // string.Empty
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
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
I have a string that starts with a JSON object but after the end of which the string goes on (something like {"a":"fdfsd","b":5}ghresd). The text afterward can contain any character and the JSON can be anything allowed for a JSON.
I would like to deserialize the JSON object and know where it ends because I want to process the rest of the string afterward, how do I do that, preferably using Newtonsoft.Json?
You could make use of the SupportMultipleContent property, for example:
var json = "{\"a\":\"fdfsd\",\"b\":5}ghresd";
var reader = new JsonTextReader(new StringReader(json));
reader.SupportMultipleContent = true;
//Read the first JSON fragment
reader.Read();
var serializer = new JsonSerializer();
var result = serializer.Deserialize(reader);
//Or if you have a class to deserialise into:
//var result = serializer.Deserialize<YourClassHere>(reader);
//Line position is where the reader got up to in the JSON string
var extraData = json.Substring(reader.LinePosition);
This piece of code might not work as expected if your json has multiple lines:
var extraData = json.Substring(reader.LinePosition);
You might need to consider adding additional check:
if(reader.LineNumber != 1)
throw new NotSupportedException("Multiline JSON objects are not supported.");
Or you can take that value from private field using Reflection:
var charPosition = (int)reader.GetType().GetField("_charPos", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(reader);
JsonTextReader source code
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 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