Error deserializing JSON with Newtonsoft, C# - javascript

I'm receiving the following error:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.List`1[ReportingDataSchema.CurrentBusinessUnits]]'
because the type requires a JSON object (e.g. {\"name\":\"value\"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {\"name\":\"value\"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array.
JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. Path 'selectedBusinessUnits', line 1, position 26."} System.Exception {Newtonsoft.Json.JsonSerializationException}
My JSON:
{
"selectedBusinessUnits": [{
"guidNode": "some value",
"businessUnit": "some value",
"fileName": "some value"
}, {
...
}]
}
I'm trying to first transform this response into the following:
public class EnAFileGenerator
{
private Dictionary<string, List<CurrentBusinessUnits>> selectedBusinessUnits;
public Dictionary<string, List<CurrentBusinessUnits>> SelectedBusinessUnits
{
get { return selectedBusinessUnits; }
set { selectedBusinessUnits = value; }
}
}
So that ultimately I can access the array in the JSON, using the following:
public class CurrentBusinessUnits
{
private string guidNode;
private string businessUnit;
private string fileName;
public string GuidNode { get; set; }
public string BusinessUnit { get; set; }
public string FileName { get; set; }
}
The code that's generating the error:
// JSON Data is the parameter from the client, containing the above JSON object
EnAFileGenerator resultArray = JsonConvert.DeserializeObject<EnAFileGenerator>(JSONData);
From what I've read, it seems like my error is a result of the parsing the array (the value for the property selectedBusinessUnits) into the desired C# collection.
After implementing #DavidG's suggestion, I'm still receiving the following:
Error converting value \"{\"guidNode\":\"some value\",\"businessUnit\":\"some value\",\"fileName\":\"some value.xlsx\"}\"
to type 'ReportingDataSchema.CurrentBusinessUnits'. Path 'selectedBusinessUnits[0]', line 1, position 159."}
System.Exception {Newtonsoft.Json.JsonSerializationException}
Prany's solution nearly got me there. I was able to modify that code to utilize the objects I already had:
var files = JObject.Parse(JSONData);
var recList = files.SelectToken("$..selectedBusinessUnits").ToList();
foreach (string item in recList)
{
JObject businessUnit = JObject.Parse(item);
CurrentBusinessUnits currentBusinessUnit = businessUnit.ToObject<CurrentBusinessUnits>();
}

The problem is you are trying to deserialise into the wrong type. you have specified a Dictionary<string, List<CurrentBusinessUnits>> but really you only need a List<CurrentBusinessUnits>:
public class EnAFileGenerator
{
public List<CurrentBusinessUnits> SelectedBusinessUnits { get; set; }
}

You can use Jobject since you're using Newtonsoft. For getting values based on selectedBusinessUnits. Use below
var files = JObject.Parse(YourJson);
var recList = files.SelectToken("$..selectedBusinessUnits").ToList();
foreach (JObject item in recList)
{
foreach (JProperty prop in item.Children())
{
string key = prop.Name.ToString();
string value = prop.Value.ToString();
}
}

Related

Replacing System.Windows.Forms.WebBrowser with Microsoft.Web.WebView2.WinForms.WebView2 in WinForms apps

I am upgrading a WinForms C# .NET 4.8 Framework application and replacing the embedded browser System.Windows.Forms.WebBrowser with Microsoft.Web.WebView2.WinForms.WebView2. I’m invoking some browser-hosted JavaScript functions from my C# code, and also my JavaScript functions do invoke some C# methods.
I’ve made it working (one of the information sources was the article https://weblog.west-wind.com/posts/2021/Jan/14/Taking-the-new-Chromium-WebView2-Control-for-a-Spin-in-NET-Part-1#do-you-need-the-webview2), however, I’m not feeling comfortable with the serialization/deserialization in my code. What bothers me is that the exchange is based on serialized strings; that approach appears somewhat primitive to me. To invoke a JavaScript function, I’m composing a source statement. Other way around, when my browser script is invoking a C# method, the argument is a serialized string (for which I know is JSON formatted) with all quote characters escaped.
My question is: should I have realized the interaction between C# and JavaScript in a different way?
Here are two C# code fragments with JavaScript invocation from a WinForms test program I’ve produced to cope with the migration:
public struct ConfigGraph3D
{
public bool showServices { get; set; }
public string viewStyle { get; set; }
public bool enforceNodeLabels { get; set; }
public bool enforceServiceLabels { get; set; }
public bool planesVisible { get; set; }
public bool embedded { get; set; }
}
// retrieving a JavaScript structure into a C# structure
//var CONFIG = { showServices: true, viewStyle: "O", enforceNodeLabels: false,
// enforceServiceLabels: false, planesVisible: true, embedded: false};
//
string invocation = "getConfig()";
string response = await browser.ExecuteScriptAsync(invocation);
// Notice here that I’m deserializing the data twice: first removing backslash escapes
// then converting it into the structure ConfigGraph3D.
string configStr = JsonSerializer.Deserialize<string>(response);
ConfigGraph3D config = JsonSerializer.Deserialize<ConfigGraph3D>(configStr);
textConfig.Text = response;
//
// sending a graph structure to JavaScript.
// The properly formatted JSON structure is read from a file by yet it needs to be serialized
// in order to add escape characters (backslash).
//
string invocation = getJsonFile (graphFilePath) ;
string response = await browser.ExecuteScriptAsync(invocation);
//
//
string getJsonFile (string graphFilePath)
{
string contents = File.ReadAllText(graphFilePath);
JsonSerializerOptions jso = new JsonSerializerOptions();
jso.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
string contents2 = JsonSerializer.Serialize(contents, jso);
string invocation = "replaceModelList(" + contents2 + ")";
return invocation;
}
Invoking C# from JavaScript example:
public struct ItemSelectedInfo
{
public int id { get; set; }
public string type { get; set; }
}
//
// invoked from JavaScript
// var jsonObj = "{ \"id\": " + mesh._id2 + ", \"type\":\"" + mesh._type + "\"}";
// window.chrome.webview.hostObjects.sync.dotnet.SelectedinGraphic(jsonObj);
//
[ComVisible(true)]
public class GraphInterface
{
public void SelectedinGraphic(string jsonInfo)
{
ItemSelectedInfo selectedNodeKey = JsonSerializer.Deserialize<ItemSelectedInfo>(jsonInfo);
Debug.WriteLine("SelectedinGraphic: " + jsonInfo);
}
}

Is it possible to have a method in C# that implicitly deserializes an argument if it's passed as a JSON string?

Question
I have a handful of ViewComponents that look like so:
public IViewComponentResult Invoke(BuyerViewModel buyer)
I'd like them to be able to accept either a BuyerViewModel, or a JSON string representing a BuyerViewModel. For example, when you pass JSON to a controller method from JavaScript, if that method expects an argument of type Dog, the controller automatically attempts to deserialize the JSON to an instance of Dog. I'm trying to mimic that behavior.
The goal would be that both of these examples work:
var buyer = new BuyerSummaryViewModel() { FirstName = "John" };
ViewComponent("Buyer", buyer);
ViewComponent("Buyer", "{\"Name\":\"John Smith\"}");
Why?
I'm trying to make a generic JavaScript method that can fetch a ViewComponent on the fly:
const fetchViewComponent = async (viewComponentName, viewModel) => {
let data = { viewComponentName, viewModel };
let html = await $.get(`/Order/FetchViewComponent`, data);
return html;
}
//Get a BuyerViewComponent (example)
(async () => {
let component = await fetchViewComponent("Buyer", `#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Buyer))`);
console.log(component);
})();
What I've Tried
If I specify that the ViewModel is a BuyerViewModel, it works. The JSON string is automatically deserialized into a BuyerViewModel.
public class FetchViewComponentRequest
{
public string ViewComponentName { get; set; }
public BuyerViewModel ViewModel { get; set; }
// ^^^^^^^^^^^^^^
}
[HttpGet]
public IActionResult FetchViewComponent(FetchViewComponentRequest request)
{
return ViewComponent(request.ViewComponentName, request.ViewModel);
}
The Issue
However, I don't want to specify the type; I want this to be generic. So I tried this:
public class FetchViewComponentRequest
{
public string ViewComponentName { get; set; }
public string ViewModel { get; set; }
// ^^^^^^
}
[HttpGet]
public IActionResult FetchViewComponent(FetchViewComponentRequest request)
{
return ViewComponent(request.ViewComponentName, request.ViewModel);
}
But as expected, request.ViewModel isn't the correct type; it ends up null in the Invoke method. I was hoping there was a flag or something more global I could specify so that it tries to implicitly deserialize this string into the expected type.
Is there an easier way to do this that I haven't considered? Or, if not, is the way I'm envisioning even possible?
(I'm using .NET Core 2.2)
Maybe make your FetchViewComponentRequest generic?
public class FetchViewComponentRequest<T>
{
public string ViewComponentName { get; set; }
public T ViewModel { get; set; }
// ^^^^^^^^^^^^^^
}
[HttpGet]
public IActionResult FetchViewComponent(FetchViewComponentRequest<BuyerViewModel> request)
{
return ViewComponent(request.ViewComponentName, request.ViewModel);
}
The method needs to have some knowledge of what type to make the object coming in.
public T Convert<T>(dynamic obj) where T:class,new()
{
T myob = null;
if (obj !=null && obj is T)
{
myob = obj as T;
}
else if (obj is string)
{
//convert to type
myob = JsonConvert.DeserializeObject<T>(obj);
}
return myob;
}
Ok, im not sure about what you need.
But here is a dynamic way to do it, without specifying <T>.
//Assume that the namespace is DynamicTypeDemo
public class DynamicType {
// eg "DynamicTypeDemo.Cat, DynamicTypeDemo"
public string TypeName { get; set; } // the full path to the type
public string JsonString { get; set; }
}
Now you could simple DeserializeObject
public object ToObject(DynamicType dynamicType){
var type = Type.GetType(dynamicType.TypeName);
// Here you could check if the json is list, its really upp to you
// but as an example, i will still add it
if (dynamicType.JsonString.StartsWith("[")) // its a list
type =(List<>).MakeGenericType(type);
return JsonConvert.DeserializeObject(dynamicType.JsonString, type);
}
And here is how it work
var item = new DynamicType(){
TypeName = "DynamicTypeDemo.Cat, DynamicTypeDemo", // or typeof(Cat).AssemblyQualifiedName
JsonString = "{CatName:'Test'}"; // And for a list "[{CatName:'Test'}]"
}
object dynamicObject= ToObject(item); // return it to the javascript
Cat cat = dynamicObject as Cat; // Cast it if you want

Passing dates between c# and javascript

I have an ASP.Net MVC app that returns a view model, which when converted to JSON using system.web.mvc.jsonresult looks as follows:
On the client I'm using KnockoutJS. I use MomentJS to format the value for the VoucherDate so that it can be displayed for humans:
var recsArray = [];
$.each(data.Vouchers, function (key, value) {
recsArray.push(
new edited(
interchangeId,
value.SupplierIsValid,
value.VoucherNo,
value.LegacySupplierId,
value.Transactions,
moment(value.OriginalVoucher.VoucherDate).format('YYYY/MM/DD HH:mm'),
value.OriginalVoucher
)
);
As you can see from the previous code snippet, in addition to pushing the data into an observable array for display in a KOGrid, I also push the entire "OriginalVoucher". This enables the user to edit the value for "LegacySupplierId" and click "Resubmit" which posts back the entire view model as seen below:
self.resubmit = function () {
var data = {
Vouchers: ko.toJS(this.recs),
BatchId: self.batchId(),
InterchangeId: interchangeId,
IsReadWrite: self.isReadWrite,
Interface: self.interface,
ReportClient: self.reportClient
};
$.ajax({
type: "POST",
url: BASE_URL + 'EditBatch/ResubmitRejectedVouchersAsNewBatch',
data: ko.toJSON(data),
I've checked using Fiddler and confirmed that the VoucherDate fields contain values such as /Date(14543712000000)/. My problem is, when reading in the C# controller, all of the dates are presented as 1/1/0001 12:00:00 AM.
I've read that javascript has a date.toISOString() function to convert to a format that C# will be happy with. Is there a better way that trying to find each date field in the javascript view model and executing a conversion against each before posting back to the C# controller
I think I have previously solved this problem using automapper by following instructions here: enter link description here
In my source code I can see that I created the following class but I don't know how / if this gets used:
public class JsonDateTimeTypeConvertor : ITypeConverter<string, DateTime>
{
public DateTime Convert(ResolutionContext context)
{
string jsonDate = context.SourceValue.ToString();
string offsetAsString = Regex.Match(jsonDate, #"\d+").Value;
double offset = System.Convert.ToDouble(offsetAsString);
DateTime ret = DataUtils.ConvertFromUnixTimestamp(offset);
return ret;
}
}
I think it was being used but I've inadvertently disabled it. This probably happened when I changed the type for the ViewModel received by the controller. The previous version of the application worked, so I guess the datetime was being converted correctly - probably by this automapper extension. I have pulled the old version of the code from TFS but can't figure out how it's doing the magic - I wish I'd made better notes at the time! All I have is:
I needed to create a custom type convertor to deal with JSON dates being passed to the EditBatch controller in the format of number of milliseconds since 1900.
I followed the wiki documentation from here:
https://github.com/AutoMapper/AutoMapper/wiki/Custom-type-converters
May be you can use a regex pattern like this. The following code shows ToJavaScriptDate() function that does this for you:
function ToJavaScriptDate(value)
{
var pattern = /Date\(([^)]+)\)/;
var results = pattern.exec(value);
var dt = new Date(parseFloat(results[1]));
return (dt.getMonth() + 1) + "/" + dt.getDate() + "/" + dt.getFullYear();
}
The ToJavaScriptDate() function accepts a value in /Date(ticks)/ format and returns a date string in MM/dd/yyyy format. Inside, the ToJavaScriptDate() function uses a regular expression that represents a pattern /Date(([^)]+))/.
The exec() method accepts the source date value and tests for a match in the value. The return value of exec() is an array. In this case the second element of the results array (results[1]) holds the ticks part of the source date. For example, if the source value is /Date(836418600000)/ then results[1] will be 836418600000. Based on this ticks value a JavaScript Date object is formed. The Date object has a constructor that accepts the number of milliseconds since 1 January 1970. Thus dt holds a valid JavaScript Date object. The ToJavaScriptDate() function then formats the date as MM/dd/yyyy and returns to the caller.
I'm really not a fan of the way the default JavaScriptConverter handles dates. I use the following class. (The CustomString class is kind of a dirty hack to get around the fact that the Serialize command is expected to return an IDictionary. See blog post here: http://blog.calyptus.eu/seb/2011/12/custom-datetime-json-serialization/)
public class DateTimeJsonSerializer : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (dictionary == null)
return null;
return new JavaScriptSerializer().ConvertToType(dictionary, type);
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
if (!(obj is DateTime)) return null;
return new CustomString(((DateTime) obj).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
}
public override IEnumerable<Type> SupportedTypes
{
get { return new ReadOnlyCollection<Type>(new List<Type> { typeof(DateTime), typeof(DateTime?) }); }
}
}
public class CustomString : Uri, IDictionary<string, object>
{
public CustomString(string str) : base(str, UriKind.Relative)
{}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool Remove(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
public int Count { get; private set; }
public bool IsReadOnly { get; private set; }
public bool ContainsKey(string key)
{
throw new NotImplementedException();
}
public void Add(string key, object value)
{
throw new NotImplementedException();
}
public bool Remove(string key)
{
throw new NotImplementedException();
}
public bool TryGetValue(string key, out object value)
{
throw new NotImplementedException();
}
public object this[string key]
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public ICollection<string> Keys { get; private set; }
public ICollection<object> Values { get; private set; }
}
To use this in asp.net, I have the following section in my web.config (make sure to replace "AssemblyNameGoesHere" with the assembly that contains the converter class:
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="2147483644">
<converters>
<add name="DateTimeConverter" type="DateTimeJsonSerializer,AssemblyNameGoesHere" />
</converters>
</jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>
I managed to work-around this by changing the data type on the "VoucherDate" field in the C# model from DateTime to String. I don't currently understand why this works. I also think there must be a better way!?

Image->JSON->.Net Image

I am trying to learn Knockout and trying to create a photo uploader. I have successfully stored some images in an Array. Now I want to post back. In my knockout code (Javascript), I do this:
I have an image 'class' in Javascript:
// The image object that wukk be posted back.
function Image(_Image, _Description, _Filesize, _Filetype) {
var self = this;
self.Image =_Image;
self.Description = _Description;
self.Filesize = _Filesize;
self.Filetype = _Filetype;
self.DisplaySize = ko.computed(function () {
SizeToText(self.Filesize);
});
}
I have an array of these in a property called 'images'.
When I click the save button, I try to simply stringify the array of images into 'object'.
var object = JSON.stringify({
images: self.images(),
});
I then post my object back to my .net WebAPI controller.
var uri = "/api/Photo/Upload";
$.post({ url: uri, contentType: "application/json" }, object)
.done(function (data) {
self.images.removeAll();
});
My method gets called, and I can see in the data it receives, all the image data, descriptions etc.
The method is defined as:
public int Upload(UIImageUploadList data)
And a UIImageUploadList is a class that holds a list of images:
public class UIImageUploadList
{
public List<UIImageUpload> Images { get; set; }
}
and UIImageUpload is:
public class UIImageUpload
{
public string Image { get; set; }
public string Description { get; set; }
public string FileType { get; set; }
public int FileSize { get; set; }
}
So, I try and parse the data from the 'data' field, so that I can pass it to my service/logic/data layer.
foreach(var p in data.Images)
{
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
var routes_list =
json_serializer.DeserializeObject(p.Image);
byte[] bytes = Convert.FromBase64String(p.Image);
Image image;
using (MemoryStream ms = new MemoryStream(bytes))
{
image = Image.FromStream(ms);
}
photos.Add(new Photo
{
Description = p.Description,
PhotoData = image
});
}
But it's in Base64, I think. (JSON.Stringify did that I believe).
The image property starts with this:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJY
and ends with:
AElFTkSuQmCC"
I know if it's JPG or a BMP etc.
In .Net, I try to change this into an Image type, to pass to my DB.
But it fails "DeserializeObject" saying that 'data is not a primitive JSON type'.
How should I be converting to an image that will finally be stored in the database?
Before calling Convert.FromBase64String, you will need to split the string containing the encoded image data. First, strip of the data:, then you get the MIME type for the image (image/png), then the encoding (base64) and only the remainder of the string (after the comma) can be passed into the conversion function. I don't have much experience with C# but from there on the FromStream function should be smart enough to deal with the decoded data.

ASP.NET MVC - How to "reverse" model binding to convert a C# model back to a query string representation

I have a custom javascript on the client side that I use to build up a querystring and pass over to my asp.net-mvc controller
var templateQueryString = BuildTemplate();
$.ajax({
url: '/MyController/Save?' + templateQueryString,
type: 'post',
dataType: 'json',
success: function (data) {
}
}
and on my controller all of the properties leverage the model binding so it comes in as a single object on the server side. NOTE: that this is a pretty complex object with arrays and arrays of sub objects:
public ActionResult Save(MyTemplate template)
{
}
the issue now is that I need to be able to convert from my C# object back to a string that represents "myTemplateQueryString" on the client side.
Is there any recommended way to take an object and do the "reverse" model binding. They key here is that it generates a string that I could use as a query string again in the future to pass into another asp.ent-mvc controller action.
Here is an example of the querystring that I am storing locally:
<input type="hidden" value="showIds=false&showRisks=false&
amp;statusIds=2&statusIds=1&statusIds=6&statusIds=8&
amp;statusIds=3&statusIds=9&showCompleted=0"
name="filterQueryString" id="filterQueryString">
As #haim770 said it would be easier if you used JSON in the request payload, and not the query string to pass your complex object to the server.
Regarding creating the query string from a model there is not a built-in method that does something like that or any recommended approach as far as i know. An obvious solution is to use reflection and build the query string from your properties.
Assuming your BuildTemplate class looks something like:
public class BuildTemplate
{
public bool ShowIds { get; set; }
public bool ShowRisks { get; set; }
public bool ShowCompleted { get; set; }
public int[] StatusIds { get; set; }
}
You can develop an extension method to convert any object to a QueryString. Here is some initial code you can start with:
public static class ObjectExtensions
{
public static string ToQueryString(this Object obj)
{
var keyPairs = obj.GetType().GetProperties().Select(p =>
new KeyValuePair<string, object>(p.Name.ToLower(), p.GetValue(obj, null)));
var arr = new List<string>();
foreach (var item in keyPairs)
{
if (item.Value is IEnumerable && !(item.Value is String))
{
foreach (var arrayItem in (item.Value as IEnumerable))
{
arr.Add(String.Format("{0}={1}", item.Key, arrayItem.ToString().ToLower()));
}
}
else
arr.Add(String.Format("{0}={1}", item.Key, item.Value.ToString().ToLower()));
}
return "?" + String.Join("&", arr);
}
}
Then you can easily invoke this code on any object to generate a query string:
var person = new BuildTemplate() { StatusIds = new []{ 1, 5, 8, 9 }, ShowRisks = true };
var queryString = person.ToQueryString();
This would generate a query string like:
"?showids=false&showrisks=true&showcompleted=false&statusids=1&statusids=5&statusids=8&statusids=9"
This query string should work just fine with the default model binder for the BuildTemplate class.

Categories

Resources