Can I use reflection to shorten this code? - c#

I have some code that I feel like I should be able to shorten incredibly, but I can't figure out how to do it.
I have a base class called Message and may classes that derive from it.
namespace ModalVR {
public class Message {
public string message;
public Message() {
this.message = this.ToString();
}
}
}
The subclasses get converted to JSON, and I have a function that receives this JSON and I need to create the appropriate class. However the function that does it has a huge case statement and I feel that there must be a better way of doing this. This is what that function looks like.
public Message ConstructMessageFromJSON(string JSON) {
string messageName = JsonUtility.FromJson<Message>(JSON).message;
Message derivedMessage = null;
switch(messageName) {
case "ModalVR.GetBatteryInfo": {
derivedMessage = JsonUtility.FromJson<GetBatteryInfo>(JSON);
break;
}
case "ModalVR.GetBatteryInfoResponse": {
derivedMessage = JsonUtility.FromJson<GetBatteryInfoResponse>(JSON);
break;
}
// Many more case statements snipped out
default: {
LogManager.Log("Received unknown message of " + messageName, LogManager.LogLevel.Error);
break;
}
}
return derivedMessage;
}
Is there any way I can replace this huge case statement with something simpler?
Thanks in advance
John Lawrie

Using reflection only, you can do:
string messageName = "ModalVR.GetBatteryInfo";
Type messageType = Assembly.GetAssembly(typeof(Message)).GetType(messageName);
Message derivedMessage = (Message)JsonUtility.FromJson(json, messageType);
It retrieves the Assembly in which you have defined your Message class and then search for the requested type in this assembly.

The easiest way would be to create a dictionary like that:
var typeMatches = new Dictionary<string, Type>
{
{"ModalVR.GetBatteryInfo", typeof(GetBatteryInfo)}
};
and then just get the value from it: (that's C# 7)
if (!typeMatches.TryGetValue(messageName, out var messageType))
{
LogManager.Log("Received unknown message of " + messageName, LogManager.LogLevel.Error);
return;
}
var derivedMessage = (Message) JsonUtility.FromJson(JSON, messageType);

Related

How to Dynamically convert JSON to right DTO class?

I have JSON data which I want to convert to correct type and then handle it. I'm using MONO and NewtonSoft's JSON library. I.E. JSON and object must match properties 1:1 to convert to right DTO. DTO's have unique properties always.
Both Activator.CreateInstance() and Convert.ChangeType() doesn't seem to compile.
DTOs:
class JSONDTO
{
}
class JSONCommandDTO : JSONDTO
{
public string cmd;
}
class JSONProfileDTO : JSONDTO
{
public string nick;
public string name;
public string email;
}
class JSONMessageDTO : JSONDTO
{
public string msg;
}
Server:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
class Server
{
protected static List<JSONDTO> DTOList;
static void Main()
{
DTOList = new List<JSONDTO>();
DTOList.Add(new JSONProfileDTO());
DTOList.Add(new JSONCommandDTO());
DTOList.Add(new JSONMessageDTO());
// ...
}
protected static void OnMessage (string message)
{
dynamic rawObject;
try
{
// Or try to convert to right DTO here somehow?
rawObject = JsonConvert.DeserializeObject<dynamic>(message);
} catch (JsonReaderException ex) {
// Invalid JSON
return;
}
int dtoCount = DTOList.ToArray().Length;
int errCount = 0;
JSONDTO DTOObject;
foreach (var dto in DTOList.ToList()) {
try {
// Doesn't compile:
// DTOObject = Activator.CreateInstance(dto.GetType(), rawObject);
// DTOObject = Convert.ChangeType(rawObject, dto.GetType());
break; // Match found!
} catch (Exception ex) {
// Didn't match
errCount++;
}
}
if (errCount == dtoCount) {
// Right DTO was not found
return;
}
if (DTOObject is JSONProfileDTO) {
AssignProfile((JSONProfileDTO) DTOObject);
}
else if (DTOObject is JSONCommandDTO)
{
RunCommand((JSONCommandDTO) DTOObject);
}
// etc ..
}
protected static void RunCommand (JSONCommandDTO command)
{
string cmd = command.cmd;
Console.WriteLine("command == " + cmd);
}
protected static void AssignProfile(JSONProfileDTO profile)
{
Console.WriteLine("got profile!");
}
}
}
I'm going to assume that you have not created the serialized data yourself from the DTO classes, because in that case you could simply have it include type information in the output. With this information available, the deserializer will be able to recreate the correct instance automatically.
Since this is most likely not your case, you need to solve the following problems:
Parse the JSON and create an object with corresponding properties
Determine which DTO instance matches the given data
Create the DTO instance and populate it using the object created in step 1
I'll assume that you have or can find a JSON deserializer to handle the first step.
You may have an easier way to perform step 2, but the simple approach would simply compare the property names available in the JSON data and find the DTO with an exact match. This could look something like this (using Fasterflect to assist with the reflection bits):
var types = [ typeof(JSONCommandDTO), typeof(JSONProfileDTO), typeof(JSONMessageDTO) ];
var json = deserializer.GetInstance( ... );
var jsonPropertyNames = json.GetType().Properties( Flags.InstancePublic )
.OrderBy( p => p.Name );
var match = types.FirstOrDefault( t => t.Properties( Flags.InstancePublic )
.OrderBy( p => p.Name )
.SequenceEqual( jsonPropertyNames ) );
if( match != null ) // got match, proceed to step 3
The code for step 3 could look like this:
// match is the DTO type to create
var dto = match.TryCreateInstance( json );
TryCreateInstance is another Fasterflect helper - it will automatically find a constructor to call and copy any remaining matching properties.
I hope this points you in the right direction.
I got it to work. I had to add JsonSerializerSettings with MissingMemberHandling.Error so that exception gets thrown if JSON doesn't fit into object. I was also missing Microsoft.CSharp reference.
class Server
{
protected static List<Type> DTOList = new List<Type>();
static void Main()
{
DTOList.Add(typeof(JSONProfileDTO));
DTOList.Add(typeof(JSONCommandDTO));
DTOList.Add(typeof(JSONMessageDTO));
}
protected static void OnMessage (string rawString)
{
dynamic jsonObject = null;
int DTOCount = DTOList.Count;
int errors = 0;
var settings = new JsonSerializerSettings ();
// This was important
// Now exception is thrown when creating invalid instance in the loop
settings.MissingMemberHandling = MissingMemberHandling.Error;
foreach (Type DTOType in DTOList) {
try {
jsonObject = JsonConvert.DeserializeObject (rawString, DTOType, settings);
break;
} catch (Exception ex) {
errors++;
}
}
if (null == jsonObject) {
return;
}
if (errors == DTOCount) {
return;
}
if (jsonObject is JSONProfileDTO) {
AssignProfile((JSONProfileDTO) jsonObject);
}
else if (jsonObject is JSONCommandDTO)
{
RunCommand((JSONCommandDTO) jsonObject);
}
}
}

Restricting usage of Anonymous Type in C#

I'd like to search all the places like following where the Anonymous types in Controllers is being used as follows.
if(success) {
returnData = JsonConvert.SerializeObject(new { Success = true, Message = "Operation completed successfully" });
}
else {
returnData = JsonConvert.SerializeObject(new { Success = false, Message = "Operation failed" });
}
In above case the returnData is a JsonResult and its used in our Razor views to parse the status of the AJAX requests.
I want to minimize the usage of Anonymous types in such case as this could be maintenance issue as compiler would not raise any warning/errors if any of the line is written as new { Succes = true, Message = "Operation completed successfully"} and it would result into run-time error in the client-side scripts.
Any insights on restricting such situation or detecting such instances would be appreciated.
Why not search in solution/project for this with option "Use Regular Expressions" on?
\bnew\s*{
Just don't use an anonymous type. Create a new concrete type with the data you plan to use:
public class JSONMessage
{
public string Message { get; set; }
public bool Success { get; set; }
}
Then those lines can be changed to:
if(success) {
returnData = JsonConvert.SerializeObject(new JSONMessage(){ Success = true, Message = "Operation completed successfully" });
}
else {
returnData = JsonConvert.SerializeObject(new JSONMessage(){ Success = false, Message = "Operation failed" });
}
How about wrapping up the json call so you can have run time error/assert:
First an extension to detect anonymous from here: Determining whether a Type is an Anonymous Type
public static class TypeExtension {
public static Boolean IsAnonymousType(this Type type) {
var hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
var nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
var isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
return isAnonymousType;
}
}
Then use that it your new method.
public static object JsonSeralize(object obj)
{
Debug.Assert(!obj.getType().IsAnonymousType());
return JsonConvert.SerializeObject(obj);
}
Now you can easily search for places that illegally call JsonConvert.SerializeObject directly.

How can I avoid entering strings for known keys in C#

I have the following code:
switch(first)
{
case 'A':
vm.Content = contentService.Get("0001000", vm.RowKey);
return View("Article", vm);
case 'F':
vm.Content = contentService.Get("0002000", vm.RowKey);
return View("FavoritesList", vm);
}
'A' refers to a page type of Article with a key of "0001000"
'F' refers to a page type of Favorite with a key of "0002000"
Is there a way in C# that I could avoid having to code in the keys as a string?
Some way that would allow me to code in by the key abbreviation or name
and then have C# convert this to a string?
Can I use Enum for this? This seems ideal but I am not sure how to set up an Enum.
You may think about dictionary (if I right understood your question)
//class for holding relation between the code and page name
public class Data
{
public string Code {get;set;}
public string PageName {get;set;}
}
var dic = new Dictionary<string, Data >{
{"A", new Data{Code="0001000", PageName = "Article"},
{"F", newe Data{Code="0002000", PageName="FavoritesList"}
}
and after use it like:
Data dt = null;
if(dic.TryGetValue(first, out dt)) { // *first* is parameter you use in switch
vm.Content = contentService.Get(dt.Code, vm.RowKey);
return View(dt.PageName, vm);
}
You can use enums and use extension methods to allow an alternative text output.
The enum:
public enum PageTypes
{
A,
F
}
The extension method (needs to be in the top level of your project):
public static class Extensions
{
public static string getText(this PageTypes type)
{
switch (type)
{
case PageTypes.A:
return "0001000";
case PageTypes.F:
return "0002000";
default:
return null;
}
}
}
And your code:
PageTypes type;
//assing value to type:
//type = ...
var vm.Content = contentService.Get(type.getText(), vm.RowKey);
switch (type)
{
case PageTypes.A:
return View("Article", vm);
case PageTypes.F:
return View("FavoritesList", vm);
}
Now you do not need to use strings to retrieve the values.
I would put the keys in a dictionary, e.g.
var keyData = new Dictionary(char,string);
keyData.Add('A',"0001000");
keyData.Add('A',"0001000");
keyData.Add('F',"0002000");
You could then reference them using
var value = keyData['A'];

Templated serialization of C# objects to JSON

I need to serialize objects to JSON. I would like to do it with a template instead of using data annotations (as most frameworks do). Does anybody know a good way of doing this?
A picture says more than 1000 words. I'm looking for something that looks like this:
For example, if I had a class like this:
public class Test
{
public string Key { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public Test Related { get; set; }
}
And a had template string that could look like this:
{
id: "$Key",
name: "$Name",
related: "$Related.Name"
}
I want to get a JSON object, whose properties are filled in according to Key, Name and Related.Name of the object.
Basically I'm searching for a JSON serialization method that supports templating instead.
I don't know about any library that does this for you, but it's not that hard to build it yourself.
If you have your template, you need to parse it as JSON and then replace all of the placeholders with actual values. To do that, you can use the visitor pattern.
Since JSON.NET (the JSON library I'm using) doesn't seem to have a visitor, you can create one yourself:
abstract class JsonVisitor
{
public virtual JToken Visit(JToken token)
{
var clone = token.DeepClone();
return VisitInternal(clone);
}
protected virtual JToken VisitInternal(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
return VisitObject((JObject)token);
case JTokenType.Property:
return VisitProperty((JProperty)token);
case JTokenType.Array:
return VisitArray((JArray)token);
case JTokenType.String:
case JTokenType.Integer:
case JTokenType.Float:
case JTokenType.Date:
case JTokenType.Boolean:
case JTokenType.Null:
return VisitValue((JValue)token);
default:
throw new InvalidOperationException();
}
}
protected virtual JToken VisitObject(JObject obj)
{
foreach (var property in obj.Properties())
VisitInternal(property);
return obj;
}
protected virtual JToken VisitProperty(JProperty property)
{
VisitInternal(property.Value);
return property;
}
protected virtual JToken VisitArray(JArray array)
{
foreach (var item in array)
VisitInternal(item);
return array;
}
protected virtual JToken VisitValue(JValue value)
{
return value;
}
}
And then create a specialized visitor that replaces the placeholders with actual values:
class JsonTemplateVisitor : JsonVisitor
{
private readonly object m_data;
private JsonTemplateVisitor(object data)
{
m_data = data;
}
public static JToken Serialize(object data, string templateString)
{
return Serialize(
data, (JToken)JsonConvert.DeserializeObject(templateString));
}
public static JToken Serialize(object data, JToken template)
{
var visitor = new JsonTemplateVisitor(data);
return visitor.Visit(template);
}
protected override JToken VisitValue(JValue value)
{
if (value.Type == JTokenType.String)
{
var s = (string)value.Value;
if (s.StartsWith("$"))
{
string path = s.Substring(1);
var newValue = GetValue(m_data, path);
var newValueToken = new JValue(newValue);
value.Replace(newValueToken);
return newValueToken;
}
}
return value;
}
private static object GetValue(object data, string path)
{
var parts = path.Split('.');
foreach (var part in parts)
{
if (data == null)
break;
data = data.GetType()
.GetProperty(part)
.GetValue(data, null);
}
return data;
}
}
The usage is then simple. For example, with the following template:
{
id : "$Key",
name: "$Name",
additionalInfo:
{
related: [ "$Related.Name" ]
}
}
You can use code like this:
JsonTemplateVisitor.Serialize(data, templateString)
The result then looks like this:
{
"id": "someKey",
"name": "Isaac",
"additionalInfo": {
"related": [
"Arthur"
]
}
}
You might want to add some error-checking, but other than that, the code should work. Also, it uses reflection, so it might not be suitable if performance is important.
10 years have passed since I've posted the question. Since I've been working with Node.JS and discovered Handlebars and how it is pretty easy to get it to parse JSON instead of HTML template. The Handlebars project has been converted to .NET.
You can use a special ITextEncoder to let Handlebars generate JSON:
using HandlebarsDotNet;
using System.Text;
public class JsonTextEncoder : ITextEncoder
{
public void Encode(StringBuilder text, TextWriter target)
{
Encode(text.ToString(), target);
}
public void Encode(string text, TextWriter target)
{
if (text == null || text == "") return;
text = System.Web.HttpUtility.JavaScriptStringEncode(text);
target.Write(text);
}
public void Encode<T>(T text, TextWriter target) where T : IEnumerator<char>
{
var str = text?.ToString();
if (str == null) return;
Encode(str, target);
}
}
Let's see it in action:
using HandlebarsDotNet;
var handlebars = Handlebars.Create();
handlebars.Configuration.TextEncoder = new JsonTextEncoder();
var sourceTemplate = #"{
""id"": ""{{Key}}"",
""name"": ""{{Name}}"",
""related "": ""{{Related.Name}}""
}";
var template = handlebars.Compile(sourceTemplate);
var json = template(new
{
Key = "Alpha",
Name = "Beta",
Related = new
{
Name = "Gamme"
}
});
Console.WriteLine(json);
This will write the following:
{
"id": "Alpha",
"name": "Beta",
"related ": "Gamme"
}
I did a small write-up on the topic on my blog: Handlebars.Net & JSON templates. In this blog I also discuss how to improve debugging these templates.
You can also use a Text Template file for your json template . The template engine will fill in the blanks and return you the result.
If you are using Visual Studio,
Create a .tt file ,
Mark it with TextTemplatingFilePreprocessor in Custom Tool property of the file. This will create a new class for you that takes care of processing the template.
For integrating your data in the resulted string , extend the newly generated class in a separate file , in which you pass the data (the arbitrary class from you image).
Use this to get the json formatted code;
MyData data = ...;
MyTemplatePage page = new MyTemplatePage(data);
String pageContent = page.TransformText();
Now the pageContent have the json formatted string; For more details about how to handle the .tt file , look here : Text Template Control Blocks
I had exactly the same need. I needed an end user (technical users but not developers) to be able to create their own json files that can later be filled via data.
Microsoft Teams is doing something similar with their adaptive card website:
https://adaptivecards.io/designer/
On the bottom left there is a json "template" and on the bottom right a json to load into the template.
Conclusion: Despite extensive research I have not found any .NET library doing this.
Sorry (๑•́ㅿ•̀๑).
Screenshot of adaptive card designer

Class; Struct; Enum confusion, what is better?

I have 46 rows of information, 2 columns each row ("Code Number", "Description"). These codes are returned to the client dependent upon the success or failure of their initial submission request. I do not want to use a database file (csv, sqlite, etc) for the storage/access. The closest type that I can think of for how I want these codes to be shown to the client is the exception class. Correct me if I'm wrong, but from what I can tell enums do not allow strings, though this sort of structure seemed the better option initially based on how it works (e.g. 100 = "missing name in request").
Thinking about it, creating a class might be the best modus operandi. However I would appreciate more experienced advice or direction and input from those who might have been in a similar situation.
Currently this is what I have:
class ReturnCode
{
private int _code;
private string _message;
public ReturnCode(int code)
{
Code = code;
}
public int Code
{
get
{
return _code;
}
set
{
_code = value;
_message = RetrieveMessage(value);
}
}
public string Message { get { return _message; } }
private string RetrieveMessage(int value)
{
string message;
switch (value)
{
case 100:
message = "Request completed successfuly";
break;
case 201:
message = "Missing name in request.";
break;
default:
message = "Unexpected failure, please email for support";
break;
}
return message;
}
}
The best would be both a class and an enumeration. Then you can have more descriptive identifiers than "201".
A structure would also work, but they are harder to implement correctly, so you should stick to a class unless you specifically need a structure for some reason.
You don't need to store a reference to the message in the class, you can get that when needed in the Message property. A switch is implemented using a hash table (if there are five values or more), so the lookup is very fast.
public enum ReturnIdentifier {
Success = 100,
MissingName = 201;
}
public class ReturnCode {
public ReturnIdentifier Code { get; private set; }
public ReturnCode(ReturnIdentifier code) {
Code = code;
}
public string Message {
get {
switch (Code) {
case ReturnIdentifier.Success:
return "Request completed successfuly.";
case ReturnIdentifier.MissingName:
return "Missing name in request.";
default:
return "Unexpected failure, please email for support.";
}
}
}
}
Usage:
ReturnCode code = new ReturnCode(ReturnIdentifier.Success);
If you get an integer code from somewhere, you can still use it as the enumerator values correspond to the codes:
int error = 201;
ReturnCode code = new ReturnCode((ReturnIdentifier)error);
(If the integer code doesn't correspond to any of the identifiers in the enumeration, it's still perfectly valid to do the conversion. When getting the Message value, it will end up in the default case as the value doesn't match any of the other cases.)
I think choosing a class(as you did) is a good decision. You can make the code a little more compact and readable, if you use Dictionary<int, string> for mapping codes to descriptions.
_dict.Add(100, "Description1");
_dict.Add(201, "Description2");
...............................
And RetrieveMessage:
return _dict[value];
How about deriving from Dictionary, or storing the data table in code using a Dictionary field that you can index into?
Maybe a dictionary based approach would look more elegant.
private static Dictionary<int, string> errorCodes =
new Dictionary<int, string>()
{
{100, "Request completed successfuly"},
{200, "Missing name in request."}
};
private string RetrieveMessage(int value)
{
string message;
if (!errorCodes.TryGetValue(value, out message))
message = "Unexpected failure, please email for support";
return message;
}
It will definitely be more slower (since it uses Reflection) but speaking of being compact, I think Enums With Custom Attributes is appropriate for this need. Please continue reading the comments since DescriptionAttribute is mentioned there. Something like;
public enum ErrorMessage
{
[System.ComponentModel.Description("Request completed successfuly")]
Success = 100,
[System.ComponentModel.Description("Missing name in request.")]
MissingName = 201
};
public static string GetDescription(this Enum en)
{
Type type = en.GetType();
System.Reflection.MemberInfo[] memInfo = type.GetMember(en.ToString());
if (memInfo != null && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute),
false);
if (attrs != null && attrs.Length > 0)
return ((System.ComponentModel.DescriptionAttribute)attrs[0]).Description;
}
return en.ToString();
}
static void Main(string[] args)
{
ErrorMessage message = ErrorMessage.Success;
Console.WriteLine(message.GetDescription());
}

Categories

Resources