Newtonsoft.Json - Deserialize uppercase boolean values without quotes - c#

I receive some not really ISO conform Json content from an api. The boolean values are uppercase instead of lower case.
{ "Bool": False }
Initially, I thought that should be easy to solve by using a custom JsonConverter like shown in how to get newtonsoft to deserialize yes and no to boolean.
But it looks like the JsonConverter.ReadJson method is never called. I think the reason is, that the value False is not in quotes and thus JsonTextReader never calls the converter and creates the exception.
What would be the best way to handle that scenario?
public class BoolTests
{
public class A
{
[JsonConverter(typeof(CaseIgnoringBooleanConverter))]
public bool Bool { get; set; }
}
[Theory]
[InlineData(false, "{'Bool': false}")] //ok
[InlineData(false, "{'Bool': 'False'}")] // ok
[InlineData(false, "{'Bool': False")] // fails
public void CasingMatters(bool expected, string json)
{
var actual = JsonConvert.DeserializeObject<A>(json);
Assert.Equal(expected, actual.Bool);
}
}
// taken from https://gist.github.com/randyburden/5924981
public class CaseIgnoringBooleanConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.Value.ToString().ToUpperInvariant().Trim())
{
case "TRUE":
return true;
case "FALSE":
return false;
}
// If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.
return new JsonSerializer().Deserialize(reader, objectType);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(bool);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Unfortunately, as you've discovered, invalid json is invalid, and thus not handled by normal and common json (de)serializers, such as Json.net.
Using converters and strategy settings for the deserializers will not work either as they're meant to handle things like empty-objects-returned-as-arrays or name conversion/case handling.
One naive solution would be to do a simple string replace, like
string json = invalidJson.Replace("False", "false");
This, however, has some problems:
You need to read the entire invalid json into memory, as well as create a fixed copy of it, which means you will have two entire copies of the data in memory, one bad and one better.
It would replace False inside strings as well. This may not be a problem with your data but wouldn't be easy to handle with the above approach.
A different approach would be to write a basic tokenizer that understands rudimentary JSON syntax, such as strings and numbers and identifiers, and go through the file token by token, replacing the bad identifiers. This would fix problem 2, but depending on the solution might need a more complex implementation to fix problem 1 with the memory.
A simple attempt at creating a TextReader that can be used, that will fix identifiers as they're found and otherwise understands rudimentary JSON tokens is posted below.
Note the following:
It is not really performant. It allocates temporary buffers all the time. You might want to look into "buffer renting" to handle this approach somewhat better, or even just stream directly to the buffer.
It doesn't handle numbers, because I stopped writing code at that point. I left this as an excercise. A basic number handling can be written because you're not really validating that the file is having valid JSON, so anything that will grab enough characters to constitute a number can be added.
I did not test this with really big files, only with the small example file. I replicated a List<Test> with 9.5MB of text, and it works for that.
I did not test all JSON syntax. There may be characters that should be handled but isn't. If you end up using this, create LOTS of tests!
What it does, however, is fix the invalid JSON according to the identifier(s) you've posted, and it does so in a streaming manner. This should thus be usable no matter how big a JSON file you have.
Anyway, here's the code, again note the exception regarding numbers:
void Main()
{
using (var file = File.OpenText(#"d:\temp\test.json"))
using (var fix = new MyFalseFixingTextReader(file))
{
var reader = new JsonTextReader(fix);
var serializer = new JsonSerializer();
serializer.Deserialize<Test>(reader).Dump();
}
}
public class MyFalseFixingTextReader : TextReader
{
private readonly TextReader _Reader;
private readonly StringBuilder _Buffer = new StringBuilder(32768);
public MyFalseFixingTextReader(TextReader reader) => _Reader = reader;
public override void Close()
{
_Reader.Close();
base.Close();
}
public override int Read(char[] buffer, int index, int count)
{
TryFillBuffer(count);
int amountToCopy = Math.Min(_Buffer.Length, count);
_Buffer.CopyTo(0, buffer, index, amountToCopy);
_Buffer.Remove(0, amountToCopy);
return amountToCopy;
}
private (bool more, char c) TryReadChar()
{
int i = _Reader.Read();
if (i < 0)
return (false, default);
return (true, (char)i);
}
private (bool more, char c) TryPeekChar()
{
int i = _Reader.Peek();
if (i < 0)
return (false, default);
return (true, (char)i);
}
private void TryFillBuffer(int count)
{
if (_Buffer.Length >= count)
return;
while (_Buffer.Length < count)
{
var (more, c) = TryPeekChar();
if (!more)
break;
switch (c)
{
case '{':
case '}':
case '[':
case ']':
case '\r':
case '\n':
case ' ':
case '\t':
case ':':
case ',':
_Reader.Read();
_Buffer.Append(c);
break;
case '"':
_Buffer.Append(GrabString());
break;
case char letter when char.IsLetter(letter):
var identifier = GrabIdentifier();
_Buffer.Append(ReplaceFaultyIdentifiers(identifier));
break;
case char startOfNumber when startOfNumber == '-' || (startOfNumber >= '0' && startOfNumber <= '9'):
_Buffer.Append(GrabNumber());
break;
default:
throw new InvalidOperationException($"Unable to cope with character '{c}' (0x{((int)c).ToString("x2")})");
}
}
}
private string ReplaceFaultyIdentifiers(string identifier)
{
switch (identifier)
{
case "False":
return "false";
case "True":
return "true";
case "Null":
return "null";
default:
return identifier;
}
}
private string GrabNumber()
{
throw new NotImplementedException("Left as an excercise");
// See https://www.json.org/ for the syntax
}
private string GrabIdentifier()
{
var result = new StringBuilder();
while (true)
{
int i = _Reader.Peek();
if (i < 0)
break;
char c = (char)i;
if (char.IsLetter(c))
{
_Reader.Read();
result.Append(c);
}
else
break;
}
return result.ToString();
}
private string GrabString()
{
_Reader.Read();
var result = new StringBuilder();
result.Append('"');
while (true)
{
var (more, c) = TryReadChar();
if (!more)
return result.ToString();
switch (c)
{
case '"':
result.Append(c);
return result.ToString();
case '\\':
result.Append(c);
(more, c) = TryReadChar();
if (!more)
return result.ToString();
switch (c)
{
case 'u':
result.Append(c);
for (int index = 1; index <= 4; index++)
{
(more, c) = TryReadChar();
if (!more)
return result.ToString();
result.Append(c);
}
break;
default:
result.Append(c);
break;
}
break;
default:
result.Append(c);
break;
}
}
}
}
public class Test
{
public bool False1 { get; set; }
public bool False2 { get; set; }
public bool False3 { get; set; }
}
Example file:
{
"false1": false,
"false2": "false",
"false3": False
}
Output (from LINQPad):

As said Lasse :
Invalid json should be fixed at the source.
If you really need to parse it as it is, you could replace False by "False" (as suggested by #Sinatr) if you want it as a string or false if you want it as a bool.
// If you want a string
json.Replace("False", "\"False\"");
// If you want a bool
json.Replace("False", "false");
One problem would be if a key or another value contains the "False" pattern.

Related

Accepting raw JSON Asp.net core

I have a asp.net core method, I want it to accept RAW json, I do not and will not always know the schema of the json object, so I am using dynamic types with dot notation.
This method works when I string the json escaping each character. I have tried to use the json body directly, but this did not work. So it seems my option were to Serialize and then Deserialize the json. ( very redundant) but it seems to throw error any other way if I try to use the JSON body directly.
In the debugger, everything seems to work with the Serialize and Deserialize of the object / string, but throws an error on the id(property) when I try to cast the object to string and gives the error. (In the debugger I am able to see the Id correctly though).
({Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert type 'System.Text.Json.JsonElement' to 'string')}
I really do not see why it gives the type as a string yet cannot convert it. I have even tried to remove the casting, and still receive this error.
public IActionResult Post([FromBody] ExpandoObject requestInput)
{
try
{
//Makes a JSON String
var stringObject = (string) JsonSerializer.Serialize(requestInput);
DateTime time = DateTime.UtcNow;
// Recreated the Json Object
dynamic requestObject = JsonSerializer.Deserialize<ExpandoObject>(stringObject);
// Throws Error here, yet it shows Value as the correct Id number (Value: Type String)
string reqObject = (string) requestObject.Id;
So there is no support for ExpandoObject in .NET Core, yet. MS says that maybe it will be added in .NET 5.0. Until then, you can use this JsonConverter I found on a thread. I will post the code here in case that thread goes away.
You can use it like this:
[HttpPost, Route("testPost")]
public IActionResult TestPost([FromBody] object obj) // just use "object"
{
// object is: { "hello":"world" }
var myDynamic = JsonSerializer.Deserialize<dynamic>(
JsonSerializer.Serialize(obj), new JsonSerializerOptions
{
Converters = { new DynamicJsonConverter() }
});
var test = (string)myDynamic.hello;
// test will equal "world"
return Ok();
}
Here is the converter:
/// <summary>
/// Temp Dynamic Converter
/// by:tchivs#live.cn
/// </summary>
public class DynamicJsonConverter : JsonConverter<dynamic>
{
public override dynamic Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.True)
{
return true;
}
if (reader.TokenType == JsonTokenType.False)
{
return false;
}
if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out long l))
{
return l;
}
return reader.GetDouble();
}
if (reader.TokenType == JsonTokenType.String)
{
if (reader.TryGetDateTime(out DateTime datetime))
{
return datetime;
}
return reader.GetString();
}
if (reader.TokenType == JsonTokenType.StartObject)
{
using JsonDocument documentV = JsonDocument.ParseValue(ref reader);
return ReadObject(documentV.RootElement);
}
// Use JsonElement as fallback.
// Newtonsoft uses JArray or JObject.
JsonDocument document = JsonDocument.ParseValue(ref reader);
return document.RootElement.Clone();
}
private object ReadObject(JsonElement jsonElement)
{
IDictionary<string, object> expandoObject = new ExpandoObject();
foreach (var obj in jsonElement.EnumerateObject())
{
var k = obj.Name;
var value = ReadValue(obj.Value);
expandoObject[k] = value;
}
return expandoObject;
}
private object? ReadValue(JsonElement jsonElement)
{
object? result = null;
switch (jsonElement.ValueKind)
{
case JsonValueKind.Object:
result = ReadObject(jsonElement);
break;
case JsonValueKind.Array:
result = ReadList(jsonElement);
break;
case JsonValueKind.String:
//TODO: Missing Datetime&Bytes Convert
result = jsonElement.GetString();
break;
case JsonValueKind.Number:
//TODO: more num type
result = 0;
if (jsonElement.TryGetInt64(out long l))
{
result = l;
}
break;
case JsonValueKind.True:
result = true;
break;
case JsonValueKind.False:
result = false;
break;
case JsonValueKind.Undefined:
case JsonValueKind.Null:
result = null;
break;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
private object? ReadList(JsonElement jsonElement)
{
IList<object?> list = new List<object?>();
foreach (var item in jsonElement.EnumerateArray())
{
list.Add(ReadValue(item));
}
return list.Count == 0 ? null : list;
}
public override void Write(Utf8JsonWriter writer,
object value,
JsonSerializerOptions options)
{
// writer.WriteStringValue(value.ToString());
}
}
Edited To Add:
Here is a much slicker way to handle dynamic using the converter above as pointed out by Aluan in the comments. In your Startup.cs class, add this:
services.AddControllers().AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new DynamicJsonConverter()));
Then you don't have to do any goofy stuff in your controller. You can just set the body parameter as dynamic and it magically works:
[HttpPost, Route("testPost")]
public IActionResult TestPost([FromBody] dynamic obj)
{
// object is: { "hello":"world" }
var test = (string)obj.hello;
// test will equal "world"
return Ok();
}
Way nicer!

NetSuite Custom Record Search Convert Results into List of .NET Objects

The only way I've been able to do it is through the reflection-based code below. I can't believe there's not an "easier NetSuite way" to do this though? Am I missing something basic?
After I perform a search on custom objects I get back an array of Record[], this can then be looped through and each item casted to a CustomObject.
The properties of the custom object are stored in the CustomRecord's customFieldList but the values are not immediately accessible you have to cast those the their real NetSuite type (like LongCustomFieldRef, DoubleCustomFieldRef, BooleanCustomFieldRef, StringCustomFieldRef, etc).
In order to not have to bother with this mess to get nice clean objects on my side I decided on the approach below:
Create classes with property names that match (including case) the NetSuite names and inherits from NetSuiteBase (defined below)
public class MyNetSuiteObject : NetSuiteBase //<-- Note base class
{
public string myProperty1 { get; set; }
public bool myProperty2 { get; set; }
public int myProperty3 { get; set; }
public static MyNetSuiteObject FromCustomSearchRecord(CustomRecord customRecord)
{
var ret = new MyNetSuiteObject();
ret.AssignProperties(customRecord);
return ret;
}
}
Create a base class which will inspect CustomRecords and apply property values to the .NET classes
public class NetSuiteBase
{
public void AssignProperties(CustomRecord customRecord)
{
var classProps = this.GetType().GetProperties();
foreach (var prop in classProps)
{
var propName = prop.Name;
var propValue = prop.GetValue(this, null);
//get the matching CustomFieldRef out of the customFieldList for the CustomRecord which matches our current property name
var myCustomFieldRef = customRecord.customFieldList.Where(c => c.scriptId == propName).FirstOrDefault();
if (myCustomFieldRef == null) continue;
//we can't get the value out until we cast the CustomFieldRef to its "actual" type.
var custType = myCustomFieldRef.GetType().Name;
switch (custType)
{
case "LongCustomFieldRef":
TrySetProperty(prop, ((LongCustomFieldRef)myCustomFieldRef).value.ToString());
break;
case "DoubleCustomFieldRef":
TrySetProperty(prop, ((DoubleCustomFieldRef)myCustomFieldRef).value.ToString());
break;
case "BooleanCustomFieldRef":
TrySetProperty(prop, ((BooleanCustomFieldRef)myCustomFieldRef).value.ToString());
break;
case "StringCustomFieldRef":
TrySetProperty(prop, ((StringCustomFieldRef)myCustomFieldRef).value.ToString());
break;
case "DateCustomFieldRef":
TrySetProperty(prop, ((DateCustomFieldRef)myCustomFieldRef).value.ToString());
break;
case "SelectCustomFieldRef":
TrySetProperty(prop, ((SelectCustomFieldRef)myCustomFieldRef).value.name.ToString());
break;
case "MultiSelectCustomFieldRef":
TrySetProperty(prop, ((MultiSelectCustomFieldRef)myCustomFieldRef).value.ToString());
break;
default:
Console.WriteLine("Unknown type: " + myCustomFieldRef.internalId);
break;
}
}
}
//Some of the NetSuite properties are represented as strings (I'm looking at you BOOLs), so we pass all the values from above
//as strings and them process/attempt casts
private void TrySetProperty(PropertyInfo prop, string value)
{
value = value.ToLower().Trim();
if (prop.PropertyType == typeof(string))
{
prop.SetValue(this, value);
return;
}
if (prop.PropertyType == typeof(bool))
{
if (value == "yes") value = "true";
if (value == "no") value = "false";
if (value == "1") value = "true";
if (value == "0") value = "false";
bool test;
if (bool.TryParse(value, out test))
{
prop.SetValue(this, test);
return;
}
}
if (prop.PropertyType == typeof(int))
{
int test;
if (int.TryParse(value, out test))
{
prop.SetValue(this, test);
return;
}
}
if (prop.PropertyType == typeof(double))
{
double test;
if (double.TryParse(value, out test))
{
prop.SetValue(this, test);
return;
}
}
if (prop.PropertyType == typeof(decimal))
{
decimal test;
if (decimal.TryParse(value, out test))
{
prop.SetValue(this, test);
return;
}
}
}
}
After performing a NetSuite search on custom objects, loop through the results and use the above classes to convert NetSuite result to a .NET class
for (int i = 0, j = 0; i < records.Length; i++, j++)
{
customRecord = (CustomRecord)records[i];
var myNetSuiteObject = MyNetSuiteObject.FromCustomSearchRecord(customRecord);
}
Is there some other "NetSuite way" to accomplish what I have above?
No, there is no "NetSuite" way. What you've done is far above and beyond the call of duty, if you ask me, and it's amazing. The NetSuite/SuiteTalk WSDL is just HORRENDOUS, and so many bad choices were made in the design process that I'm shocked that it was released as-is to developers without any questions raised. Below is one other example.
This is from the documentation for their SuiteTalk course, which reveals that when parsing the results of an Advanced search, they contain within them SearchRowBasic objects. Well within each SearchRowBasic object there are multiple fields. To access those fields' values, you have to take the first element of a SearchColumnField array. Why would you DO this? If there will only ever be ONE value (and they state in their documentation that yes indeed there will only ever be ONE value), WHY the heck would you make the return object an array instead of just passing back to the developer the value of the primitive type itself directly??? That's just plain bad API construction right there, forcing the developers to use SearchColumnField[0].searchValue every time rather than just SearchColumnField.searchValue!

Implementation of Enum.TryParse in .NET 3.5

How could I implement the .NET 4's Enum.TryParse method in .NET 3.5?
public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct
I dislike using a try-catch to handle any conversion failures or other non-exceptional events as part of the normal flow of my application, so my own Enum.TryParse method for .NET 3.5 and earlier makes use of the Enum.IsDefined() method to make sure the there will not be an exception thrown by Enum.Parse(). You can also include some null checks on value to prevent an ArgumentNullException if value is null.
public static bool TryParse<TEnum>(string value, out TEnum result)
where TEnum : struct, IConvertible
{
var retValue = value == null ?
false :
Enum.IsDefined(typeof(TEnum), value);
result = retValue ?
(TEnum)Enum.Parse(typeof(TEnum), value) :
default(TEnum);
return retValue;
}
Obviously this method will not reside in the Enum class so you will need a class to include this in that would be appropriate.
One limitation is the lack of an enum constraint on generic methods, so you would have to consider how you want to handle incorrect types. Enum.IsDefined will throw an ArgumentException if TEnum is not an enum but the only other option is a runtime check and throwing a different exception, so I generally do not add an additional check and just let the type checking in these methods handle for me. I'd consider adding IConvertible as another constraint, just to help constrain the type even more.
Took longer than I hoped to get this right, but it works and has been tested. Hope this saves someone some time!
private static readonly char[] FlagDelimiter = new [] { ',' };
public static bool TryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct {
if (string.IsNullOrEmpty(value)) {
result = default(TEnum);
return false;
}
var enumType = typeof(TEnum);
if (!enumType.IsEnum)
throw new ArgumentException(string.Format("Type '{0}' is not an enum", enumType.FullName));
result = default(TEnum);
// Try to parse the value directly
if (Enum.IsDefined(enumType, value)) {
result = (TEnum)Enum.Parse(enumType, value);
return true;
}
// Get some info on enum
var enumValues = Enum.GetValues(enumType);
if (enumValues.Length == 0)
return false; // probably can't happen as you cant define empty enum?
var enumTypeCode = Type.GetTypeCode(enumValues.GetValue(0).GetType());
// Try to parse it as a flag
if (value.IndexOf(',') != -1) {
if (!Attribute.IsDefined(enumType, typeof(FlagsAttribute)))
return false; // value has flags but enum is not flags
// todo: cache this for efficiency
var enumInfo = new Dictionary<string, object>();
var enumNames = Enum.GetNames(enumType);
for (var i = 0; i < enumNames.Length; i++)
enumInfo.Add(enumNames[i], enumValues.GetValue(i));
ulong retVal = 0;
foreach(var name in value.Split(FlagDelimiter)) {
var trimmedName = name.Trim();
if (!enumInfo.ContainsKey(trimmedName))
return false; // Enum has no such flag
var enumValueObject = enumInfo[trimmedName];
ulong enumValueLong;
switch (enumTypeCode) {
case TypeCode.Byte:
enumValueLong = (byte)enumValueObject;
break;
case TypeCode.SByte:
enumValueLong = (byte)((sbyte)enumValueObject);
break;
case TypeCode.Int16:
enumValueLong = (ushort)((short)enumValueObject);
break;
case TypeCode.Int32:
enumValueLong = (uint)((int)enumValueObject);
break;
case TypeCode.Int64:
enumValueLong = (ulong)((long)enumValueObject);
break;
case TypeCode.UInt16:
enumValueLong = (ushort)enumValueObject;
break;
case TypeCode.UInt32:
enumValueLong = (uint)enumValueObject;
break;
case TypeCode.UInt64:
enumValueLong = (ulong)enumValueObject;
break;
default:
return false; // should never happen
}
retVal |= enumValueLong;
}
result = (TEnum)Enum.ToObject(enumType, retVal);
return true;
}
// the value may be a number, so parse it directly
switch (enumTypeCode) {
case TypeCode.SByte:
sbyte sb;
if (!SByte.TryParse(value, out sb))
return false;
result = (TEnum)Enum.ToObject(enumType, sb);
break;
case TypeCode.Byte:
byte b;
if (!Byte.TryParse(value, out b))
return false;
result = (TEnum)Enum.ToObject(enumType, b);
break;
case TypeCode.Int16:
short i16;
if (!Int16.TryParse(value, out i16))
return false;
result = (TEnum)Enum.ToObject(enumType, i16);
break;
case TypeCode.UInt16:
ushort u16;
if (!UInt16.TryParse(value, out u16))
return false;
result = (TEnum)Enum.ToObject(enumType, u16);
break;
case TypeCode.Int32:
int i32;
if (!Int32.TryParse(value, out i32))
return false;
result = (TEnum)Enum.ToObject(enumType, i32);
break;
case TypeCode.UInt32:
uint u32;
if (!UInt32.TryParse(value, out u32))
return false;
result = (TEnum)Enum.ToObject(enumType, u32);
break;
case TypeCode.Int64:
long i64;
if (!Int64.TryParse(value, out i64))
return false;
result = (TEnum)Enum.ToObject(enumType, i64);
break;
case TypeCode.UInt64:
ulong u64;
if (!UInt64.TryParse(value, out u64))
return false;
result = (TEnum)Enum.ToObject(enumType, u64);
break;
default:
return false; // should never happen
}
return true;
}
It won't be a static method on Enum (static extension methods don't quite make sense), but it should work
public static class EnumHelpers
{
public static bool TryParse<TEnum>(string value, out TEnum result)
where TEnum : struct
{
try
{
result = (TEnum)Enum.Parse(typeof(TEnum), value);
}
catch
{
result = default;
return false;
}
return true;
}
}
At NLog we also needed Enum.TryParse for .Net 3.5. We have implemented the basic features (just parse, case sensitive and insensitive, no flags) influenced by this post.
This basic implementation is highly unit tested so it has the same behavior as Microsoft`s .Net 4 implementation.
/// <summary>
/// Enum.TryParse implementation for .net 3.5
///
/// </summary>
/// <returns></returns>
/// <remarks>Don't uses reflection</remarks>
// ReSharper disable once UnusedMember.Local
private static bool TryParseEnum_net3<TEnum>(string value, bool ignoreCase, out TEnum result) where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum())
throw new ArgumentException($"Type '{enumType.FullName}' is not an enum");
if (StringHelpers.IsNullOrWhiteSpace(value))
{
result = default(TEnum);
return false;
}
try
{
result = (TEnum)Enum.Parse(enumType, value, ignoreCase);
return true;
}
catch (Exception)
{
result = default(TEnum);
return false;
}
}
And is using:
public static class StringHelpers
{
/// <summary>
/// IsNullOrWhiteSpace, including for .NET 3.5
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[ContractAnnotation("value:null => true")]
internal static bool IsNullOrWhiteSpace(string value)
{
#if NET3_5
if (value == null) return true;
if (value.Length == 0) return true;
return String.IsNullOrEmpty(value.Trim());
#else
return string.IsNullOrWhiteSpace(value);
#endif
}
}
The code can be found at the NLog GitHub, and also the unit tests are on GitHub (xUnit)

generic method to validate int, double. How to use GetType()?

I'm trying to write a validation method. Eg: for double it looks like this:
protected bool ValidateLoopAttributes(string i_value, double i_threshold)
{
double result;
if (!(double.TryParse(i_value, out result) && result >= i_threshold))
{
return false;
}
return true;
}
Is it possible to write this as:
protected bool ValidateLoopAttributes<T>(string i_value, T i_threshold)
and then use something like
T.GetType().TryParse() // how can i use here the type's methods??
Is using a switch/if statement the only way to do this? Eg:
If (T.GetType() is int)
Int32.TryParse(i_threshold)
Is there a more elegant way?
Try this:
static class Ext
{
public static bool TryParse<T>(string s, out T value)
{
TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
try
{
value = (T)converter.ConvertFromString(s);
return true;
}
catch
{
value = default(T);
return false;
}
}
public static bool ValidateLoopAttributes<T>(string i_value, T i_threshold)
where T : IComparable
{
T outval;
if (TryParse<T>(i_value, out outval))
return outval.CompareTo(i_threshold) >= 0;
else return false;
}
}
My answer uses Marc Gravell's answer taken from here.
With this you can do
bool b1 = Ext.ValidateLoopAttributes<int>("5", 4);
bool b2 = Ext.ValidateLoopAttributes<double>("5.4", 5.5d);
If you find it useful you can also use an extension method
public static bool ValidateLoopAttributes<T>(this string i_value, T i_threshold)
where T : IComparable { }
which leads you to use
bool b1 = "5".ValidateLoopAttributes<int>(4);
bool b2 = "5.4".ValidateLoopAttributes<double>(5.5d);
Currently you are mixing two things inside your method - parsing and business rules. Consider you invoke ValidateLoopAttributes(value, 4) and it returns false. Possible reasons:
String does not contain value. E.g. empty, some characters, etc.
String does not contain integer value. E.g. it has double value.
String contains integer value, but it exceeds threshold.
No converters defined for your type.
In first case you have invalid data in your source.
In second case you have invalid code, which should use double instead.
In third case code is OK, but business rule was broken.
In last case (which is not case for doubles or integers, but if you write generic code with no restrictions on type, you allow others to call it with any type) also problem in code.
So, think about separating business rules and parsing data.
Foo foo = Parse(xml);
RunBusinessRules(foo);
public static bool ValidateLoopAttributes<T>(string value, T threshold)
where T : IComparable
{
try
{
var parseMethod = typeof(T).GetMethod("Parse", new[] {typeof (string)});
var result = (T) parseMethod.Invoke(null, new object[] {value});
return result.CompareTo(threshold) < 0;
}
catch (Exception)
{
return false;
}
}
Obviously, this only works for types with a static Parse method.
Can try to use something like this to check if this is an integer or not:
public static bool IsNumericValue(string val, System.Globalization.NumberStyles NumberStyle)
{
double result;
return Double.TryParse(val,NumberStyle,
System.Globalization.CultureInfo.CurrentCulture,out result);
}
so on
IsNumericValue("1.2", System.Globalization.NumberStyles.Integer) // FALSE
and on
IsNumericValue("12", System.Globalization.NumberStyles.Integer) // TRUE
Pay attention that in this example I used CurrectCulture, fit it to your needs, if they are different.

JavaScriptSerializer and ValueTypes (struct)

For a project i've created several struct in C#.
The probject itself is a ASP.Net MVC 2 project.
snip:
struct TDummy
{
private char _value;
public TDummy(char value)
{
this._value = value; // Restrictions
}
}
I created this because I needed to restrict a char-variable to a specific number of values. (I could have created an Enum, but these values are also used in the database, and then i would still need to convert them)
Now i need to create a JsonResult, like
return Json(new { Value = new TDummy('X') });
But when I do this, I get a result of:
{"Value":{}}
I expected to get a result of
{"Value":"X"}
I've tried several things, like TypeConverter (CanConvertTo(string)), Custom Type Serializer (JavaScriptSerializer.RegisterConverters()), but either they don't work or they must return a 'Complex' json-object.
{"Value":{"Name":"Value"}}
Any thoughts on this?
I want to serialize a value-type as a value...
If anyone is interested, i've create a small demo (console app) to illustrate this:
public struct TState
{
public static TState Active = new TState('A');
public static TState Pending = new TState('P');
private char _value;
public TState(char value)
{
switch (value)
{
case 'A':
case 'P':
this._value = value; // Known values
break;
default:
this._value = 'A'; // Default value
break;
}
}
public static implicit operator TState(char value)
{
switch (value)
{
case 'A':
return TState.Active;
case 'P':
return TState.Pending;
}
throw new InvalidCastException();
}
public static implicit operator char(TState value)
{
return value._value;
}
public override string ToString()
{
return this._value.ToString();
}
}
public class Test { public TState Value { get; set; } }
class Program
{
static void Main(string[] args)
{
Test o = new Test();
o.Value = 'P'; // From Char
char c = o.Value; // To Char
Console.WriteLine(o.Value); // Writes 'P'
Console.WriteLine(c); // Writes 'P'
JavaScriptSerializer jser = new JavaScriptSerializer();
Console.WriteLine(jser.Serialize(o)); // Writes '{"Value":{}}'
Console.ReadLine();
}
}

Categories