mardi 24 avril 2018

How to force c# NewtonSoft deserializer to use already created object for deserialization

While doing

JsonConvert.DeserializeObject(value, type);

A new object is created of given type for deserialization. What my requirement is I want to use already created object for deserialization

CustomObject obj = new CustomObject(); //passing constructor args
JsonConvert.DeserializeObject(value, obj);    

But this overload is not available.

So, is there any workaround so that I can use the already created object instead of creating new type through Deserializer.?

Why do I want to do so? (Detail)

I am writing a generic mapper from JSON to c# classes. Structure of JSON looks like

  {
    "ActionType" : "LogEvent", //this is actually class name in a specific namespace
    "Settings"   : {
           "FailureRetries" : 4,
           "LogChannel"     : 1234 // log channel id, implementation detail
     }
  }

What I do in the configuration mapper is as follows

public void IAction GetActionFromConfig(){

    dynamic config = configManager.Load<dynamic>(); //loads JSON file.
    string className = config.ActionType; //from config we will get file name.
    Type type = Type.GetType(nameSpaceForConfig + "." + className);

    string  settingValue = JsonConvert.SerializeObject(config.Settings);
    object actionObject = JsonConvert.DeserializeObject(settingValue, type);

    return (IAction)actionObject; // return the IAction from config.
}

My problem is the IAction implementation classes might have some dependencies in the constructor (such as ILogger) and by default, deserialization won't initialize the constructor parameter. I know I can override the JsonConverter for the constructor initialization but as I am writing a generic mapper I can't give the specific arguments using custom JsonConverter.

What I am planning to do is first create a dictionary that will map Type (c# Type class) to a specific implementation for dependency resolution.

Second I will get the constructors of given action type (type given in config with ActionType parameter)

Type type = Type.GetType(nameSpaceForConfig + "." + className);
var ctors = typeof(type).GetConstructors();
var ctor = ctors[0];
List<Type> constructorArguments = new List<Type();
foreach (var param in ctor.GetParameters())
{
     constructorArguments.Add(param.ParameterType);
}

Then I will query the dictionary created in step 1. to get the arguments for the constructor.

Finally, I will use the Activator.CreateInstance() (passing the constructor dependencies got in previous step).

And then use this object for deserialization to initialize the public properties through the config file.

So, this is why I require deserialization to happen on the object not on the type.

NOTE: Pre-condition is each action should have one constructor with all the dependencies.

Aside: configManager.Load<T> implementation

public T Load<T>() where T : class, new() => Load(typeof(T)) as T;
public object Load(Type type)
{
  if (!File.Exists(_configurationFilePath))
    return Activator.CreateInstance(type);

  var jsonFile = File.ReadAllText(_configurationFilePath);

  return JsonConvert.DeserializeObject(jsonFile, type, JsonSerializerSettings);
}





Aucun commentaire:

Enregistrer un commentaire