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);
}
}
}
Related
I need to deserialize this weird JSON (image below). I've seen some deserialization hints using Dictionary<>, etc. but the problem is that "parameters" contains different data, then previous keys.
Can I somehow get it to work using JsonSerializer deserializator without doing foreach loops and other suspicious implementations? I do need data from "data" in my application.
Here's some of my code:
using var client = new WebClient();
var json = client.DownloadString(GetJsonString());
var invoicesData = JsonSerializer.Deserialize<JsonMyData>(json, options);
If using Newtonsoft is necessary I might start using it.
With Newtonsoft you can parse and access arbitrary JSON documents, even ones that can't reasonably be deserialized into a .NET object. So something like:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
namespace ConsoleApp35
{
class Program
{
static void Main(string[] args)
{
var json = #"
{
""myData"" :
{
""0"" : { ""data"": { ""A"":1,""B"":2} },
""1"" : { ""data"": { ""A"":1,""B"":2} },
""2"" : { ""data"": { ""A"":1,""B"":2} },
""3"" : { ""data"": { ""A"":1,""B"":2} },
""parameters"" : { ""p"":""a""}
},
""status"":{ }
}";
var foo = JObject.Parse(json);
var a = foo["myData"]["1"]["data"];
Console.WriteLine(a);
Console.WriteLine("Hit any key to continue");
Console.ReadKey();
}
}
}
I think you should really consider using Newtonsoft.Json instead of default JsonDeserializer, it is much easier to use in such situations.
If you are interested in processing this without foreach loops and wanting to access the data in a list format, I would suggest using Dictionary for this. When you use dictionary, you can use Objects as values that would compensate for differences in numbers (0, 1, 2, ..) and words (parameters).
// Classes to Deserialize data we need.
public class MyObject
{
[JsonProperty("data")]
public Data Data { get; set; }
}
public class Data
{
public int A { get; set; }
public int B { get; set; }
}
Usage in Main
// Read in the JSON
var myData = JsonConvert.DeserializeObject<dynamic>(jsonString)["myData"];
// Convert To Dictionary
Dictionary<string, dynamic> dataAsObjects = myData.ToObject<Dictionary<string, dynamic>>();
string searchFor = "3";
dataAsObjects.TryGetValue(searchFor, out dynamic obj);
if (obj != null)
{
// Conversion to int and matching against searchFor is to ensure its a number.
int.TryParse(searchFor, out int result);
if (result == 0 && result.ToString().Equals(searchFor))
{
MyObject myObject = obj.ToObject<MyObject>();
Console.WriteLine($"A:{myObject.Data.A} - B:{myObject.Data.B}");
}
else if (result == 8 && result.ToString().Equals(searchFor))
{
// I am not clear on whats your parameters class look like.
MyParameters myParams = obj.ToObject<MyParameters>();
}
}
Output
A:1 - B:2
With this method you can either access the numbers or the parameters element.
NPoco (a .NET micro ORM, derived from PetaPoco) has a method for bulk-inserting records into a database, given a list of a generic type. The method signature is:
void InsertBulk<T>(IEnumerable<T> pocos);
Internally it takes the name of the type T and uses it to determine the DB table to insert into (similarly the type's property names are mapped to the column names). Therefore it is critically important that a variable of the correct type is passed to the method.
My challenge is this:
I am given a list of objects to insert into the DB, as List<IDataItem> where IDataItem is an interface that all insertable objects' classes must implement
The list may contain objects of any type that implements IDataItem, and there may be a mixture of types in the list
To underline the problem - I do not know at compile time the actual concrete type that I have to pass to InsertBulk
I have tried the following approach, but the result of Convert.ChangeType is Object, so I am passing a list of Objects to InsertBulk, which is invalid.
private static Exception SaveDataItemsToDatabase(List<IDataItem> dtos)
{
using (var db = new DbConnection())
{
try
{
var dtosByType = dtos.GroupBy(x => x.GetType());
db.Data.BeginTransaction();
foreach (var dataType in dtosByType)
{
var type = dataType.Key;
var dtosOfType = dataType.Select(x => Convert.ChangeType(x, type));
db.Data.InsertBulk(dtosOfType);
}
db.Data.CommitTransaction();
return null;
}
catch (Exception ex)
{
db.Data.RollbackTransaction();
return ex;
}
}
}
Is there any way I can accomplish this?
You have to create a new list of type List<T> and copy all your items to it, then call InsertBulk via reflection.
foreach(var g in groups)
{
var dataItemType = g.Key;
var listType = typeof(List<>).MakeGenericType(new [] { dataItemType });
var list = (IList) Activator.CreateInstance(listType);
foreach(var data in g)
list.Add(data);
db.Data.GetType()
.GetMethod("InsertBulk")
.MakeGenericMethod(dataItemType)
.Invoke(db.Data, new object[] { list });
}
See it working here: https://dotnetfiddle.net/BS2FLy
You could try something like this:
private static Exception SaveDataItemsToDatabase(List<IDataItem> dtos)
{
using (var db = new DbConnection())
{
try
{
var dtosByType = dtos.GroupBy(x => x.GetType());
db.Data.BeginTransaction();
var method = db.Data.GetType().GetMethod("InsertBulk");
foreach (var dataType in dtosByType)
{
var genericMethod = method.MakeGenericMethod(dataType.Key);
genericMethod.Invoke(db.Data, new object[] { dataType.Value };
}
db.Data.CommitTransaction();
return null;
}
catch (Exception ex)
{
db.Data.RollbackTransaction();
return ex;
}
}
}
This code might help you to do what you want (though a bit hacky).
class Program {
static void Main() {
var items = new IDataItem[] {
new TestItem(),
new TestItem(),
new TestItem2(),
new TestItem2(),
};
foreach (var kv in items.GroupBy(c => c.GetType())) {
// group by actual type
var type = kv.Key;
var batch = kv.ToArray();
// grab BulkInsert<Type> method
var insert = typeof(Test).GetMethod("BulkInsert").MakeGenericMethod(type);
// create array of Type[]
var casted = Array.CreateInstance(type, batch.Length);
Array.Copy(batch, casted, batch.Length);
// invoke
insert.Invoke(new Test(), new object[] { casted});
}
Console.ReadKey();
}
}
public interface IDataItem {
}
public class TestItem : IDataItem {
}
public class TestItem2 : IDataItem
{
}
public class Test {
public void BulkInsert<T>(IEnumerable<T> items) {
Console.WriteLine(typeof(T).Name);
}
}
If use your original code, it will be something like:
private static Exception SaveDataItemsToDatabase(List<IDataItem> dtos)
{
using (var db = new DbConnection())
{
try
{
db.Data.BeginTransaction();
foreach (var dataType in dtos.GroupBy(x => x.GetType())) {
var type = dataType.Key;
var items = dataType.ToArray();
var insert = db.Data.GetType().GetMethod("BulkInsert").MakeGenericMethod(type);
// create array of Type[]
var casted = Array.CreateInstance(type, items.Length);
Array.Copy(items, casted, items.Length);
// invoke
insert.Invoke(db.Data, new object[] {casted});
}
db.Data.CommitTransaction();
return null;
}
catch (Exception ex)
{
db.Data.RollbackTransaction();
return ex;
}
}
}
I'm going to take an educated guess on this one since I don't have the code to run it on my side.
How about:
private static Exception SaveDataItemsToDatabase(List<IDataItem> dtos)
{
using (var db = new DbConnection())
{
try
{
db.Data.BeginTransaction();
dtos
.GroupBy(dto => dto.GetType())
.ForEach(grp => {
db.Data.BulkInsert(dtos.Where(n => n.GetType().Equals(grp.Key).ToList());
});
db.Data.CommitTransaction();
return null;
}
catch (Exception ex)
{
db.Data.RollbackTransaction();
return ex;
}
}
}
Is there a way to make a function return the type of object I pass in? I would like to call the one method below to return the type I pass in. Is that possible? Should I even be trying to do this? Is there a better way...short of having two different methods?
Currently, I tried the first two calls and I get back (with the first call) what looks like a dictionary with a system.object[] in the value part of the dictionary. Screen shot below might show it better than my explanation. I ask this as I might have more types that I need to deserialize and don't want to have a different method for each.
var firstTry = this.Deserialize(path, typeof(ObservableCollection<ListItemPair>();
var secondTry = this.Deserialize(path, typeof(ListItemPair));
var thirdTry = this.Deserialize(path, typeof(SomeOtherObject));
public static object Deserialize(string jsonFile, object type)
{
var myObject = new object();
try
{
using (StreamReader r = new StreamReader(jsonFile))
{
var serializer = new JavaScriptSerializer();
string json = r.ReadToEnd();
myObject = serializer.Deserialize<object>(json);
}
}
catch (Exception ex)
{
}
return myObject ;
}
public class ListItemPair
{
public string Name
{
get;
set;
}
public object Value
{
get;
set;
}
}
object created:
Yes, you can create a generic method. Your Deserialize() method would look something like this:
public static T Deserialize<T>(string jsonFile)
{
T myObject = default(T);
try
{
using (var r = new StreamReader(jsonFile))
{
var serializer = new JavaScriptSerializer();
string json = r.ReadToEnd();
myObject = serializer.Deserialize<T>(json);
}
}
catch (Exception ex)
{
}
return myObject;
}
In this example T is a type parameter. When invoking this method, you can pass the type like this:
var firstTry = Deserialize<ObservableCollection<ListItemPair>>(path);
var secondTry = Deserialize<ListItemPair>(path);
var thirdTry = Deserialize<SomeOtherObject>(path);
One side note: I wouldn't recommend silently swallowing an exception. In this case, it is expected that the deserialization can fail. Therefore, I would change it to a TryDeserialize() method:
public static bool TryDeserialize<T>(string jsonFile, out T myObject)
{
try
{
using (var r = new StreamReader(jsonFile))
{
var serializer = new JavaScriptSerializer();
string json = r.ReadToEnd();
myObject = serializer.Deserialize<T>(json);
}
}
catch (Exception ex)
{
myObject = default(T);
return false;
}
return true;
}
I know that the same problem is faced by a lot of people in one way or another but what I'm confused about is that how come Newtonsoft JSON Serializer is able to correctly handle this case while JavaScriptSerializer fails to do so.
I'm going to use the same code sample used in one of the other stackoverflow thread (JavascriptSerializer serializing property twice when "new" used in subclass)
void Main()
{
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var json = serializer.Serialize(new Limited());
Limited status = serializer.Deserialize<Limited>(json); --> throws AmbiguousMatchException
}
public class Full
{
public String Stuff { get { return "Common things"; } }
public FullStatus Status { get; set; }
public Full(bool includestatus)
{
if(includestatus)
Status = new FullStatus();
}
}
public class Limited : Full
{
public new LimitedStatus Status { get; set; }
public Limited() : base(false)
{
Status = new LimitedStatus();
}
}
public class FullStatus
{
public String Text { get { return "Loads and loads and loads of things"; } }
}
public class LimitedStatus
{
public String Text { get { return "A few things"; } }
}
But if I use Newtonsoft Json Serializer, everythings works fine. Why? And is it possible to achieve the same using JavaScriptSerializer?
void Main()
{
var json = JsonConvert.SerializeObject(new Limited());
Limited status = JsonConvert.DeserializeObject<Limited>(json); ----> Works fine.
}
The reason this works in Json.NET is that it has specific code to handle this situation. From JsonPropertyCollection.cs:
/// <summary>
/// Adds a <see cref="JsonProperty"/> object.
/// </summary>
/// <param name="property">The property to add to the collection.</param>
public void AddProperty(JsonProperty property)
{
if (Contains(property.PropertyName))
{
// don't overwrite existing property with ignored property
if (property.Ignored)
return;
JsonProperty existingProperty = this[property.PropertyName];
bool duplicateProperty = true;
if (existingProperty.Ignored)
{
// remove ignored property so it can be replaced in collection
Remove(existingProperty);
duplicateProperty = false;
}
else
{
if (property.DeclaringType != null && existingProperty.DeclaringType != null)
{
if (property.DeclaringType.IsSubclassOf(existingProperty.DeclaringType))
{
// current property is on a derived class and hides the existing
Remove(existingProperty);
duplicateProperty = false;
}
if (existingProperty.DeclaringType.IsSubclassOf(property.DeclaringType))
{
// current property is hidden by the existing so don't add it
return;
}
}
}
if (duplicateProperty)
throw new JsonSerializationException("A member with the name '{0}' already exists on '{1}'. Use the JsonPropertyAttribute to specify another name.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName, _type));
}
Add(property);
}
As you can see above, there is specific code here to prefer derived class properties over base class properties of the same name and visibility.
JavaScriptSerializer has no such logic. It simply calls Type.GetProperty(string, flags)
PropertyInfo propInfo = serverType.GetProperty(memberName,
BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
This method is documented to throw an exception in exactly this situation:
Situations in which AmbiguousMatchException occurs include the following:
A type contains two indexed properties that have the same name but different numbers of parameters. To resolve the ambiguity, use an overload of the GetProperty method that specifies parameter types.
A derived type declares a property that hides an inherited property with the same name, using the new modifier (Shadows in Visual Basic). To resolve the ambiguity, include BindingFlags.DeclaredOnly to restrict the search to members that are not inherited.
I don't know why Microsoft didn't add logic for this to JavaScriptSerializer. It's really a very simple piece of code; perhaps it got eclipsed by DataContractJsonSerializer?
You do have a workaround, which is to write a custom JavaScriptConverter:
public class LimitedConverter : JavaScriptConverter
{
const string StuffName = "Stuff";
const string StatusName = "Status";
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var limited = new Limited();
object value;
if (dictionary.TryGetValue(StuffName, out value))
{
// limited.Stuff = serializer.ConvertToType<string>(value); // Actually it's get only.
}
if (dictionary.TryGetValue(StatusName, out value))
{
limited.Status = serializer.ConvertToType<LimitedStatus>(value);
}
return limited;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
var limited = (Limited)obj;
if (limited == null)
return null;
var dict = new Dictionary<string, object>();
if (limited.Stuff != null)
dict.Add(StuffName, limited.Stuff);
if (limited.Status != null)
dict.Add(StatusName, limited.Status);
return dict;
}
public override IEnumerable<Type> SupportedTypes
{
get { return new [] { typeof(Limited) } ; }
}
}
And then use it like:
try
{
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new LimitedConverter() });
var json = serializer.Serialize(new Limited());
Debug.WriteLine(json);
var status = serializer.Deserialize<Limited>(json);
var json2 = serializer.Serialize(status);
Debug.WriteLine(json2);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString()); // NO ASSERT.
}
I'm doing an application where I have the following scenario:
I have several rules (business classes)
where they all return the client code. They are separate classes that will look for the code trial and error, if find the client code returns it and so on.
How can I use a rule without using a bunch of IFs or threaded IFs in the class that calls the others that contains the specific business rules?
For the specific classes, I used the design pattern strategy.
EX: Main Class
public abstract class Geral
{
public abstract string retornaCodigo(Arquivo cliente)
{
var codigo = ""; // logica
return codigo;
}
}
public class derivada1 : Geral
{
public override string retornaCodigo(Arquivo cliente)
{
var codigo = ""; // logica
return codigo;
}
}
public class derivada2 : Geral
{
public override string retornaCodigo(Arquivo cliente)
{
var codigo = ""; // logica 2
return codigo;
}
}
public class derivada3 : Geral
{
public override string retornaCodigo(Arquivo cliente)
{
var codigo = ""; // logica 3
return codigo ;
}
}
public class Negocio
{
public string Codigo()
{
var arquivo = new Arquivo();
var derivada1 = new derivada1().retornaCodigo(arquivo);
var derivada2 = new derivada2().retornaCodigo(arquivo);
var derivada3 = new derivada3().retornaCodigo(arquivo);
if (derivada1.Equals(null))
return derivada1;
if (derivada2.Equals(null))
return derivada2;
if (derivada3.Equals(null))
return derivada3;
return "";
}
}
what I wanted and that I did not have to use Ifs in the Business class for validation whether or not I found the code where it can fall under any condition gave example of 3 classes plus I have more than 15 conditions and can increase, case would be many Ifs.
Let's organize all derivada into a collection, say, array and then query the collection with a help of Linq
public string Codigo() {
var arquivo = new Arquivo();
Geral[] derivadas = new [] {
new derivada1(),
new derivada2(),
new derivada3();
};
//TODO: check the the condition: I guessed that you want to return first meanful codigo
foreach (var codigo in derivadas.Select(geral => geral.retornaCodigo(arquivo)))
if (!string.IsNullOrEmpty(codigo))
return codigo;
return "";
}
If you have a lot of derivada you can try using Reflection in order to create a collection:
using System.Reflection;
...
private static Geral[] s_Derivadas = AppDomain
.CurrentDomain
.GetAssemblies() // scan assemblies
.SelectMany(asm => asm.GetTypes()) // types within them
.Where(t => !t.IsAbstract) // type is not abstract
.Where(t => typeof(Geral).IsAssignableFrom(t)) // type derived from Geral
.Where(t => t.GetConstructor(Type.EmptyTypes) != null) // has default constructor
.Select(t => Activator.CreateInstance(t) as Geral) // create type's instance
.ToArray(); // materialized as array
then
public string Codigo() {
var arquivo = new Arquivo();
foreach (var codigo in s_Derivadas.Select(geral => geral.retornaCodigo(arquivo)))
if (!string.IsNullOrEmpty(codigo))
return codigo;
return "";
}
You could create a list of derivada's and then iterate over it
and if any given derivada1 equals None, you simply return it, otherwise you just continue the 'for loop'
I could write up a snippet if this doesn't make sense to you. lmk!
This would be simple with Linq:
public class Negocio
{
public string Codigo()
{
var arquivo = new Arquivo();
var derivadaList = new List<Geral>() {
new derivada1(),
new derivada2(),
new derivada3(),
};
return derivadaList.FirstOrDefault(d => d.retornaCodigo(arquivo) == null)?.retornaCodigo(arquivo) ?? "";
}
}
You can add as many Geral derived classes to the derivadaList as you want and the code will continue to function as designed.
What is happening here is that FirstOrDefault will run the Lamda expression on every element returning the first one that equals null (although I'm not sure this is what you want, it matches your example code). Since it returns a Geral object, you need to call retornaCodigo on it only if it is not null. If it is null, just return an empty string.
Another way to write this would be:
public class Negocio
{
public string Codigo()
{
var arquivo = new Arquivo();
var derivadaList = new List<Geral>() {
new derivada1(),
new derivada2(),
new derivada3(),
};
foreach (var derivada in derivadaList)
{
var result = derivada.retornaCodigo(arquivo);
if (result == null)
return result;
}
return "";
}
}
You can also use a list of derived classes and call them in Loop
public string Codigo()
{
var arquivo = new Arquivo();
List<Geral> gerals=new List<Geral>();
gerals.Add(new derivada1());
gerals.Add(new derivada2());
........
...........
foreach(Geral g in gerals)
{
var val=g.retornaCodigo(arquivo);
if(val!=null)
return val;
}
return "";
}
This is a sample implementation, However you are not using strategy correctly
A better approach will be constructor injection,
public string Codigo(Geral implementar)
{
var val=geral.retornaCodigo(arquivo);
return "";
}
Then instantiate only with the chosen strategy.
Otherwise if you want to chain multiple validations, then use CHain of responsibility pattern.