"SearchAsync" InvalidCastException Null object cannot be converted to a value type - azure-cognitive-search

I use Azure search and a field in the search index can be null.
Which means my code
var result = await searchIndexClient.Documents.SearchAsync<SearchEntity>(query, parameters);
throws an exception
"InvalidCastException Null object cannot be converted to a value type."
for Csharp:
public class SearchEntity{
...
boolean NewField{ get; set; }
...
}
and index document:
{
"#odata.context": "...",
"value": [
{
"#search.score": 1,
...
"NewField": null,
...
I would like to tell SearchAsync(?) to set a default value to the property in SearchEntity if the received field is null.
Is it possible?
(I know I can receive a null and default it later but what is the fun in that?)

One possible solution would be to define the default values in your entity and tell the JSON serializer to ignore the null values from source at the time of serialization.
For example, consider the following entity definition:
public class SearchEntity
{
[JsonProperty(PropertyName = "newField")]
public bool NewField { get; set; } = true;
[JsonProperty(PropertyName = "dateTime")]
public DateTime DateTime { get; set; } = DateTime.Now.Date;
}
and here's the code for serialization. I am using NewtonSoft.Json for that and instructing it to ignore the null values:
string json = "{\"newField\": false, \"dateTime\": null}";
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore
};
var searchEntity = JsonConvert.DeserializeObject<SearchEntity>(json, settings);
Console.WriteLine($"SearchEntity.NewField: {searchEntity.NewField}");//Prints "true"
Console.WriteLine($"SearchEntity.DateTime: {searchEntity.DateTime.ToString("u")}");//Prints current date e.g. "2021-07-01 00:00:00Z"

Related

Mapster from ExpandoObject to Dto - how to prevent setting missing keys to Null in Dto?

I have an object representing an update:
var update = new ExpandoObject();
update.AddSafely("Name", "Mary");
This property is part of a dto, like:
public class Dto
{
public string Name {get; set;}
public string Other {get; set;
}
var data = new Dto { Name = "John", Other = "SomeData" };
Now when I map, all (other) properties not in the source dictionary are set to their default.
// Name = "Mary" but Other = null
update.Adapt(data);
You need to use IgnoreNullValues

MVC Custom Validation String Array Client Side

There are examples out there of custom MVC validators that take an array parameter, but only server side - none of them show an example of implementing the client side with array parameter.
The problem is instead of outputting the array's contents in the html data- attribute, it outputs "System.String[]":
data-val-total-propertynames="System.String[]"
Here is my attribute class:
public class TotalAttribute : ValidationAttribute, IClientValidatable
{
private String[] PropertyNames { get; set; }
public TotalAttribute(String[] propertyNames)
{
PropertyNames = propertyNames;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
float total = 0;
foreach (var propertyName in PropertyNames)
total += (float)context.ObjectInstance.GetType().GetProperty(propertyName).GetValue(context.ObjectInstance, null);
if (total != (float)value)
return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "total",
};
rule.ValidationParameters["propertynames"] = PropertyNames;
yield return rule;
}
}
Here it is implemented in the model:
[Total(new string[] { "SomeOtherField1", "SomeOtherField2" }, ErrorMessage = "'Line12Balance' must equal total of 'SomeOtherField1' and 'SomeOtherField2'")]
public decimal? Line12Balance { get; set; }
Here is the html data-val attribute output:
data-val-total-propertynames="System.String[]"
What am I doing wrong?
You get "System.String[]" string because the value of ValidationParameters["propertynames"] is written by calling ToString on it, so string[] variable returns "System.String[]" in this case. If you need to output specific value you need to format it by yourself in your validation attribute. For example, change
rule.ValidationParameters["propertynames"] = PropertyNames;
to
rule.ValidationParameters["propertynames"] = string.Join(",", PropertyNames);
and you will get
data-val-total-propertynames="SomeOtherField1,SomeOtherField2"
As Alexander mentioned in his answer, the issue you encountered is that string[] doesn't have its own ToString() implementation, and uses the base object.ToString() implementation, which just displays the type of the object rather than the contents of your array.
So you've got to somehow serialize your string array so that it can be stored in a string to then parse in your client-side JavaScript.
If you're already using JSON.Net in your solution (as many are), you can also do this by JSON serializing the array on the server side, then parsing the JSON client-side.
i.e. server-side:
rule.ValidationParameters["propertynames"] = JsonConvert.SerializeObject(PropertyNames);
Client-side: (
$.validator.addMethod('rule', function(value, element, params) {
var propertynames = JSON.parse(params.propertynames);
// do your validation
});
That prevents you from having to escape commas/delimiters in your parameters.

How to deserialize dodgy JSON (with improperly quoted strings, and missing brackets)?

I am having to parse (and ultimately reserialize) some dodgy JSON. it looks like this:
{
name: "xyz",
id: "29573f59-85fb-4d06-9905-01a3acb2cdbd",
status: "astatus",
color: colors["Open"]
},
{
name: "abc",
id: "29573f59-85fb-4d06-9905-01a3acb2cdbd",
status: "astatus",
color: colors["Open"]
}
There are a number of problems here - starting with the most severe.
color: colors["Open"]
WTF even is that? If I drop 'colors' then I can get an array of strings out but I can't tweak to work out of the box.
It is an array without square brackets. I can fix this by wrapping in them. But is there a way to support out of the box?
Properties have no quotes. Deserializing is fine for these.. but reserializing is just no dice.
Any suggestions of handling both in and out of this structure?
Answering your questions #1 - #3 in order:
Json.NET does not support reading dodgy property values in the form colors["Open"] (which, as you correctly note, violates the JSON standard).
Instead, you will need to manually fix these values, e.g. through some sort of Regex:
var regex = new Regex(#"(colors\[)(.*)(\])");
var fixedJsonString = regex.Replace(jsonString,
m => string.Format(#"""{0}{1}{2}""", m.Groups[1].Value, m.Groups[2].Value.Replace("\"", "\\\""), m.Groups[3].Value));
This changes the color property values into properly escaped JSON strings:
color: "colors[\"Open\"]"
Json.NET does, however, have the capability to write dodgy property values by calling JsonWriter.WriteRawValue() from within a custom JsonConverter.
Define the following converter:
public class RawStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var s = (string)value;
writer.WriteRawValue(s);
}
}
Then define your RootObject as follows:
public class RootObject
{
public string name { get; set; }
public string id { get; set; }
public string status { get; set; }
[JsonConverter(typeof(RawStringConverter))]
public string color { get; set; }
}
Then, when re-serialized, you will get the original dodgy values in your JSON.
Support for deserializing comma-delimited JSON without outer brackets will be in the next release of Json.NET after 10.0.3. see Issue 1396 and Issue 1355 for details. You will need to set JsonTextReader.SupportMultipleContent = true to make it work.
In the meantime, as a workaround, you could grab ChainedTextReader and public static TextReader Extensions.Concat(this TextReader first, TextReader second) from the answer to How to string multiple TextReaders together? by Rex M and surround your JSON with brackets [ and ].
Thus you would deserialize your JSON as follows:
List<RootObject> list;
using (var reader = new StringReader("[").Concat(new StringReader(fixedJsonString)).Concat(new StringReader("]")))
using (var jsonReader = new JsonTextReader(reader))
{
list = JsonSerializer.CreateDefault().Deserialize<List<RootObject>>(jsonReader);
}
(Or you could just manually surround your JSON string with [ and ], but I prefer solutions that don't involve copying possibly large strings.)
Re-serializing a root collection without outer braces is possible if you serialize each item individually using its own JsonTextWriter with CloseOutput = false. You can also manually write a , between each serialized item to the underlying TextWriter shared by every JsonTextWriter.
Serializing JSON property names without a surrounding quote character is possible if you set JsonTextWriter.QuoteName = false.
Thus, to re-serialize your List<RootObject> without quoted property names or outer braces, do:
var sb = new StringBuilder();
bool first = true;
using (var textWriter = new StringWriter(sb))
{
foreach (var item in list)
{
if (!first)
{
textWriter.WriteLine(",");
}
first = false;
using (var jsonWriter = new JsonTextWriter(textWriter) { QuoteName = false, Formatting = Formatting.Indented, CloseOutput = false })
{
JsonSerializer.CreateDefault().Serialize(jsonWriter, item);
}
}
}
var reserializedJson = sb.ToString();
Sample .Net fiddle showing all this in action.

How to add types information to JSON on serialization?

Angular requires Date objects in many places whereas JSON contains string representation of the date.
I want to add an array of properties which contain date values:
class Foo
{
public int IntProp {get;set;}
public DateTime? Prop1 {get;set;}
public DateTime Prop2 {get;set;}
public Bar Bar {set;set;}
}
class Bar
{
public DateTime Prop {get;set;}
public IEnumerable<DateTime?> Dates {get;set;}
}
Foo should then be serialized like this:
{
"IntProp": 1,
"Prop1": "...",
"Prop2": "...",
"Bar": {
"Prop": "..."
},
"<Dates>": [ "Prop1", "Prop2", "Bar.Prop", "Bar.Dates"]
}
This allows me to automatically convert strings to date objects at the client side without testing every property whether it is convertible to Date like it is described in this question.
I can collect the paths of date properties, but have no idea how to add populated array to the root.
You could convert to an intermediate JObject and add the property there. For instance, given the following converter:
public class PathLoggingDateTimeConverter : IsoDateTimeConverter
{
public const string DatePathPropertyName = "<Dates>";
readonly List<string> paths = new List<string>();
public override bool CanConvert(Type objectType)
{
if (!base.CanConvert(objectType))
return false;
// Not for DateTimeOffset
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
base.WriteJson(writer, value, serializer);
if (value != null)
paths.Add(writer.Path);
}
public IList<string> Paths { get { return paths; } }
}
You can do:
var root = new Foo
{
IntProp = 101,
Prop1 = DateTime.Today.ToUniversalTime(),
Prop2 = DateTime.Today.ToUniversalTime(),
Bar = new Bar
{
Prop = DateTime.Today.ToUniversalTime(),
Dates = new List<DateTime?> { null, DateTime.Today.ToUniversalTime() },
},
};
var converter = new PathLoggingDateTimeConverter();
var settings = new JsonSerializerSettings { Converters = new[] { converter } };
var obj = JObject.FromObject(root, JsonSerializer.CreateDefault(settings));
obj[PathLoggingDateTimeConverter.DatePathPropertyName] = JToken.FromObject(converter.Paths);
Console.WriteLine(obj);
And the result is:
{
"IntProp": 101,
"Prop1": "2016-10-25T04:00:00Z",
"Prop2": "2016-10-25T04:00:00Z",
"Bar": {
"Prop": "2016-10-25T04:00:00Z",
"Dates": [
null,
"2016-10-25T04:00:00Z"
]
},
"<Dates>": [
"Prop1",
"Prop2",
"Bar.Prop",
"Bar.Dates[1]"
]
}

convert string to bool while fetching data from database

create table tasktodo(Id int identity(1,1) not null, Done bit,Texts nvarchar(max),Dates date)
That is my database table.
I wrote the code to get the data from database like this
public List<TaskToDoList> GetTaskToDo()
{
var Obj = DBHelper.GetDBObject();
reader = Obj.ExecuteReader(CommandType.StoredProcedure, "GetTaskToDoList");
var tasktodo = new List<TaskToDoList>();
while(reader.Read())
{
tasktodo.Add(new TaskToDoList
{
Id =GetInteger("Id"),
Done=Convert.ToBoolean("Done"),
Text=GetString("Text"),
Date =reader["Date"] ==DBNull.Value ? DateTime.MinValue : Convert.ToDateTime(reader["Date"])
});
}
return tasktodo;
}
My model
public class TaskToDoList
{
public int Id { get; set; }
public bool Done { get; set; }
public string Text { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
}
It is showing an error
"String was not recognized as a valid Boolean"
How to solve this?
I got the answer for it.
I'm reading the values from the reader those GetString and GetIngers are the functions to check any nulls. those parameter are values from database table
public List<TaskToDoList> GetTaskToDo()
{
var Obj = DBHelper.GetDBObject();
reader = Obj.ExecuteReader(CommandType.StoredProcedure, "GetTaskToDoList");
var tasktodo = new List<TaskToDoList>();
while(reader.Read())
{
tasktodo.Add(
new TaskToDoList
{
Id =GetInteger("Id"),
Done=Convert.ToBoolean( reader["Done"]),
Text=GetString("Texts"),
Date =reader["Dates"] ==DBNull.Value ? DateTime.MinValue : Convert.ToDateTime(reader["Dates"])
});
}
return tasktodo;
}

Resources