I am making C# code generation with T4 for an ASP.NET Web API project... I need a simple way to convert an EdmType to JsonSchemaType.
Thanks!
Since time flies, I ended up writing a switch, until I find a better solution...
public static class TypeMapper
{
public static JsonSchemaType Convert(EdmType edmType)
{
switch (edmType.BuiltInTypeKind )
{
case BuiltInTypeKind.EnumType:
return JsonSchemaType.String;
case BuiltInTypeKind.ComplexType:
return JsonSchemaType.Object;
case BuiltInTypeKind.PrimitiveType:
return GetPrimitiveType(edmType);
default:
return JsonSchemaType.Null;
}
}
private static JsonSchemaType GetPrimitiveType(EdmType edmType)
{
switch (edmType.Name)
{
case "String":
case "Guid":
case "DateTime":
return JsonSchemaType.String;
case "Int32":
return JsonSchemaType.Integer;
case "Single":
case "Double":
return JsonSchemaType.Float;
default:
return JsonSchemaType.Null;
}
}
}
Related
I have the following json string:
[
{
"Key":"A",
"Value":null
},
{
"Key":"B",
"Value":"18"
},
{
"Key":"C",
"Value":"False"
},
{
"Key":"D",
"Value":"BOB"
}
]
I would like to be able to deserialize into the following objects:
public class ModelOne
{
public int? A { get; set; }
public int B { get; set;}
}
public class ModelTwo
{
public bool C { get; set; }
public string D { get; set; }
}
We thought about using var model = JsonConvert.DeserializeObject<ModelOne>(json); but clearly the json string is a list of Key and Value so that wouldn't work.
In an ideal world we would like to parse the json and match the Key to the Property Name and set the Value according to the property type. We could use a similar function to the above which accepts an anonymous type we're just not sure where to start so would be very greatful for some feedback and or assistance.
Thanks in advance.
EDIT:
The json array represents some data points we receive from an external api call.
ModelOne and ModelTwo are each view models in our MVC project we would like to pre-populate.
Thanks very much for all of your comments but notably both of #mjwills and #Heretic Monkey you really helped.
In the (end against my better judgement) I decided to use a little reflection.
public T ConvertDataMapToModel<T>(T item, List<Data> list)
{
Type itemType = typeof(T);
var response = (T)item;
var props = response.GetType().GetProperties();
foreach (var prop in props)
{
string propName = prop.Name;
string listValue = (string)(from c in list where c.Key == prop.Name select c.Value).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(listValue) && !string.IsNullOrEmpty(listValue))
{
PropertyInfo pInstance = itemType.GetProperty(propName);
Type pInstancePropertyType = pInstance.PropertyType;
if (pInstancePropertyType.IsGenericType && pInstancePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
pInstancePropertyType = pInstancePropertyType.GetGenericArguments()[0];
}
TypeCode typeCode = Type.GetTypeCode(pInstancePropertyType);
switch (typeCode)
{
case TypeCode.Boolean:
pInstance.SetValue(response, Convert.ToBoolean(listValue));
break;
case TypeCode.Byte:
pInstance.SetValue(response, Convert.ToByte(listValue));
break;
case TypeCode.Char:
pInstance.SetValue(response, Convert.ToChar(listValue));
break;
case TypeCode.DateTime:
pInstance.SetValue(response, Convert.ToDateTime(listValue));
break;
case TypeCode.DBNull:
pInstance.SetValue(response, Convert.DBNull);
break;
case TypeCode.Decimal:
pInstance.SetValue(response, Convert.ToDecimal(listValue));
break;
case TypeCode.Double:
pInstance.SetValue(response, Convert.ToDouble(listValue));
break;
case TypeCode.Empty:
pInstance.SetValue(response, "");
break;
case TypeCode.Int16:
pInstance.SetValue(response, Convert.ToInt16(listValue));
break;
case TypeCode.Int32:
pInstance.SetValue(response, Convert.ToInt32(listValue));
break;
case TypeCode.Int64:
pInstance.SetValue(response, Convert.ToInt64(listValue));
break;
case TypeCode.SByte:
pInstance.SetValue(response, Convert.ToSByte(listValue));
break;
case TypeCode.Single:
pInstance.SetValue(response, Convert.ToSingle(listValue));
break;
case TypeCode.String:
pInstance.SetValue(response, Convert.ToString(listValue));
break;
case TypeCode.UInt16:
pInstance.SetValue(response, Convert.ToUInt16(listValue));
break;
case TypeCode.UInt32:
pInstance.SetValue(response, Convert.ToUInt32(listValue));
break;
case TypeCode.UInt64:
pInstance.SetValue(response, Convert.ToUInt64(listValue));
break;
}
}
}
return response;
}
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.
Today I created a factory for operator based Expression.
But when I create my factory, it has some dependency on comparison value.
Here is my code
public LambdaExpression GetPredicate(FieldType fieldType, GigFilterOption value)
{
switch (fieldType)
{
case FieldType.PriceNumeric :
return new OperatorFactory( Convert.ToDecimal(value.LowerLimit), Convert.ToDecimal(value.UpperLimit)).GetOperator((Operator)value.Operator).Apply<YayNinja.DAL.Task>("Price");
break;
case FieldType.TimePeriod :
return new OperatorFactory(Convert.ToDecimal(value.LowerLimit), Convert.ToDecimal(value.UpperLimit)).GetOperator((Operator)value.Operator).Apply<YayNinja.DAL.Task>("Duration");
break;
case FieldType.CheckBox:
case FieldType.Radio:
return new OperatorFactory(value.LowerLimit, value.UpperLimit).GetOperator((Operator)value.Operator).Apply<TaskField>("Value");
break;
default :
throw new NotImplementedException();
};
}
Here is My Operator factory.
public class OperatorFactory : IOperatoFactory
{
private object lowerLimit;
private object upperLimit;
public OperatorFactory(object _lowerLimit, object _upperLimit) //**here you see dependency of value that i need for comparsion.**
{
lowerLimit = _lowerLimit;
upperLimit = _upperLimit;
}
public IOperatorPredicate GetOperator(Operator #operator)
{
IOperatorPredicate operatorPredicate = null;
switch (#operator)
{
case Operator.Equal:
operatorPredicate = new EqualToPredicate(lowerLimit);
break;
case Operator.GreaterThan:
operatorPredicate = new GreaterThanPredicate(lowerLimit);
break;
case Operator.GreaterThanOrEqual:
operatorPredicate = new GreaterThanEqualPerdicate(lowerLimit);
break;
case Operator.LessThan:
operatorPredicate = new LessThanPredicate(lowerLimit);
break;
case Operator.LessThanOrEqual:
operatorPredicate = new LessThanEqualPredicate(lowerLimit);
break;
case Operator.Between:
operatorPredicate = new BetweenPredicate(lowerLimit,upperLimit);
break;
default:
throw new NotImplementedException();
}
return operatorPredicate;
}
}
I m sure that factory pattern is creator pattern it only create object without any dependency.
Please guide me on this topic
How will a C# switch statement's default label handle a nullable enum?
Will the default label catch nulls and any unhandled cases?
If it's null, it will hit the default label.
public enum YesNo
{
Yes,
No,
}
public class Program
{
public static void Main(string[] args)
{
YesNo? value = null;
switch (value)
{
case YesNo.Yes:
Console.WriteLine("Yes");
break;
case YesNo.No:
Console.WriteLine("No");
break;
default:
Console.WriteLine("default");
break;
}
}
}
The program will print default.
Unless null is handled.
public class Program
{
public static void Main(string[] args)
{
YesNo? value = null;
switch (value)
{
case YesNo.Yes:
Console.WriteLine("Yes");
break;
case YesNo.No:
Console.WriteLine("No");
break;
case null:
Console.WriteLine("NULL");
break;
default:
Console.WriteLine("default");
break;
}
}
}
prints NULL.
If you have an unhandled enum value that was added later:
public enum YesNo
{
Yes,
No,
FileNotFound,
}
public class Program
{
public static void Main(string[] args)
{
YesNo? value = YesNo.FileNotFound;
switch (value)
{
case YesNo.Yes:
Console.WriteLine("Yes");
break;
case YesNo.No:
Console.WriteLine("No");
break;
default:
Console.WriteLine("default");
break;
}
}
}
It still prints default.
You can use the null-coalescing operator ?? to route null switch values to a specific case label other than default:
public static IEnumerable<String> AsStrings(this IEnumerable<Char[]> src)
{
Char[] rgch;
var e = src.GetEnumerator();
while (e.MoveNext())
{
switch ((rgch = e.Current)?.Length ?? -1)
{
case -1: // <-- value when e.Current is 'null'
yield return null;
break;
case 0:
yield return String.Empty;
break;
case 1:
yield return String.Intern(new String(rgch[0], 1));
break;
default: // 2...n
yield return new String(rgch);
break;
}
}
}
You can have a case for null.
switch (MyNullableEnum)
{
case Option1:
break;
case Option2:
break;
case Option3:
break;
case null:
break;
default:
break;
}
It's worth to mention that C# 8.0 introduced a new Property Pattern for a switch expression. Now you can implement default logic to switch by using underscore:
public double Calculate(int left, int right, Operator op) =>
op switch
{
Operator.PLUS => left + right,
Operator.MINUS => left - right,
Operator.MULTIPLY => left * right,
Operator.DIVIDE => left / right,
_ => 0 // default
}
Ref. https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8
I'm working with a database that is only capable of supporting data types that I have written code for.
I created an Enum parameter with a list of available types.
public enum TableDataType
{
None=0, String=1, Integer=2, Character=3, Boolean=4, DateTime=5, Decimal=6
}
This works, but I still have to process going in and coming back out of my data structures:
TableDataType GetMyType(DataGridViewColumn col) {
TableDataType type;
if (col.ValueType == typeof(bool)) {
type = TableDataType.Boolean;
} else if (col.ValueType == typeof(char)) {
type = TableDataType.Character;
} else if (col.ValueType == typeof(DateTime)) {
type = TableDataType.DateTime;
} else if (col.ValueType == typeof(Decimal)) {
type = TableDataType.Decimal;
} else if (col.ValueType == typeof(Int32)) {
type = TableDataType.Integer;
} else if (col.ValueType == typeof(string)) {
type = TableDataType.String;
} else {
throw new ArgumentException(string.Format("Data Type of '{0}' is not supported.", col.ValueType));
}
return type;
}
Then there is code going the other way...
Type GetSystemType(TableDataType myType) {
Type sysType;
switch (myType) {
case TableDataType.Boolean: sysType = typeof(bool); break;
case TableDataType.Character: sysType = typeof(char); break;
case TableDataType.DateTime: sysType = typeof(DateTime); break;
case TableDataType.Integer: sysType = typeof(Int32); break;
case TableDataType.Decimal: sysType = typeof(Decimal); break;
case TableDataType.String: sysType = typeof(string); break;
default: throw new ArgumentOutOfRangeException(string.Format("Data Type '{0}' is not allowed.", cd.DataType));
}
return sysType;
}
The problem comes from using routines similar to these at more than one spot in my code. If I tell a DataGridViewColumn that it is formatted for an Int32 then pass it an Integer, I get ArgumentException errors.
I'm looking for a good, fast class wrapper that cleverly stores the value and a select range of acceptable data types.
[EDIT] Solution I came up with:
Using the information provided in harpo's comment and Bas's solution, I created this class:
public static class Enumerate {
private Enumerate() {
throw new NotSupportedException();
}
public static SqlDbType ForSqlCe(System.Type item) {
return sqlDbCode(item);
}
public static SqlDbType ForSqlCe(TableDataType item) {
return sqlDbCode(item.GetType());
}
static SqlDbType sqlDbCode(System.Type item) {
switch (Type.GetTypeCode(item)) {
case TypeCode.Boolean: return SqlDbType.Bit;
case TypeCode.Byte:
case TypeCode.Char:
case TypeCode.SByte: return SqlDbType.NChar;
case TypeCode.DateTime: return SqlDbType.DateTime;
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single: return SqlDbType.Decimal;
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64: return SqlDbType.Int;
case TypeCode.String: return SqlDbType.NVarChar;
case TypeCode.DBNull:
case TypeCode.Empty:
case TypeCode.Object:
default: throw new TypeAccessException(item + " unknown");
}
}
public static TableDataType ForTableData(SqlDbType item) {
return tableDataCode(item.GetType());
}
public static TableDataType ForTableData(System.Type item) {
return tableDataCode(item);
}
static TableDataType tableDataCode(System.Type item) {
switch (Type.GetTypeCode(item)) {
case TypeCode.Boolean: return TableDataType.Boolean;
case TypeCode.Byte:
case TypeCode.Char:
case TypeCode.SByte: return TableDataType.Character;
case TypeCode.DateTime: return TableDataType.DateTime;
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single: return TableDataType.Decimal;
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64: return TableDataType.Integer;
case TypeCode.String: return TableDataType.String;
case TypeCode.DBNull:
case TypeCode.Empty:
case TypeCode.Object:
default: throw new TypeAccessException(item + " unknown");
}
}
public static Type ForWin32(string item) {
string text = item.Trim().ToLower();
switch (text) {
case "boolean":
case "bool":
case "bit": return typeof(bool);
case "byte":
case "char":
case "sbyte": return typeof(char);
case "date":
case "datetime":
case "time": return typeof(DateTime);
case "decimal":
case "double":
case "numeric":
case "single": return typeof(Double);
case "int":
case "int16":
case "int32":
case "int64":
case "integer":
case "uint16":
case "uint32":
case "uint64": return typeof(Int32);
case "string": return typeof(string);
default:
throw new TypeAccessException(item + " unknown");
}
}
public static Type ForWin32(SqlDbType item) {
return win32Code(item.GetType());
}
public static Type ForWin32(TableDataType item) {
return win32Code(item.GetType());
}
static Type win32Code(System.Type item) {
switch (Type.GetTypeCode(item)) {
case TypeCode.Boolean: return typeof(bool);
case TypeCode.Byte:
case TypeCode.Char:
case TypeCode.SByte: return typeof(char);
case TypeCode.DateTime: return typeof(DateTime);
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single: return typeof(Decimal);
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64: return typeof(Int32);
case TypeCode.String: return typeof(string);
case TypeCode.DBNull:
case TypeCode.Empty:
case TypeCode.Object:
default: throw new TypeAccessException(item + " unknown");
}
}
}
If you dont want to use System.TypeCode like harpo suggests, use Type.GetType():
string assemblyQualifiedName = "System.{0}, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
string typeString = myEnumValue.ToString();
Type type = Type.GetType(string.Format(assemblyQualifiedName, typeString));
Another option is to store the mapping in a Dictionary:
static class TypeResolver
{
static Dictionary<TableDataType, Type> typeLookup = new Dictionary<TableDataType, Type>();
static TypeResolver()
{
typeLookup.Add(TableDataType.Integer, typeof(Int32));
typeLookup.Add(TableDataType.String, typeof(String));
}
public static Type Resolve(TableDataType tableType)
{
return typeLookup[tableType];
}
}