简体   繁体   中英

Convert Class on Json Deserialization

There's a class CoupledType in my application that is used within a larger object of type UserObject , where its instances are saved by being serialized using Json.NET and stored in a database.

This class in need of substantial refactoring in order to make it work in more contexts. Its name is also not ideal, and I'd want to move it to another library. However, it would still extend the same base class, and crucially, when users deserialize their saved work, the original instance must be converted.

The class in its current state looks, roughly, like this:

namespace FirstLibrary.Objects
{
    [DataContract]
    public class CoupledType : BaseType
    {
        [DataMember]
        public double CoupledValue { get; set; } 
    }
}

And the type I want to convert it to looks like this:

namespace SecondLibrary.Objects
{
    [DataContract]
    public class DecoupledType : BaseType
    {
        [DataMember]
        GenericContract Contract { get; set; }
    }
}

The GenericContract class mentioned above looks like this:

[DataContract]
public class GenericContract
{
    [DataMember]
    public ContractType ContractType { get; set; }

    [DataMember]
    public double ContractValue { get; set; }
}

In the conversion process, I would want to create a new GenericContract object, getting the ContractType from elsewhere (its value will always be the same, in this conversion) and the ContractValue would be set to the value of the original CoupledValue from CoupledType .

The potentially trickier part is where I need access to this objects' parent UserObject (ie. the one it is being deserialized from) to get a reference to the value of ContractType .


To summarise, I need to write a converter for Json.NET which does the following:

  • Change the type name of the object from FirstLibrary.Objects.CoupledType to SecondLibrary.Objects.DecoupledType
  • Replace double CoupledValue with GenericContract Contract (the construction of which requires access to the ParentObject this CoupledType instance is a member of / being deserialized from.

I don't have a lot of experience with converting types in Json (having written a subclass of Json.Net 's JsonConverter to convert a number of double objects into double[] s. I have no experience of changing the type or property names. If anyone could at least point me towards a potential solution, it would be much appreciated.


Sample of Existing (speculative, as actually serialized in BSON)

{
    "$type": "FirstLibrary.Objects.CoupledType",
    "CoupledValue": 4
}

{
    "$type": "SecondLibrary.Objects.DecoupledType",
    "Contract": {
        "$type": "SecondLibrary.Objects.GenericContract",
        "ContractType": {/*Refers to an object serialized elsewhere*/},
        "ContractValue": 4
    }
}

If it helps, the serialization settings are as follows:

ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
TypeNameHandling = TypeNameHandling.All

I'm unsure if these approaches would fail to satisfy some unspoken requirement, but I can think of a few potential approaches that could get your DecoupledType from the wire into your controller.

You could create a custom HttpParameterBinding , and perform your mapping there. Your incoming parameter information is stored inside the HttpParameterDescriptor , and you can interrogate the parameters to determine whether or not the particular binding applies.

public class GenericContractBinding : HttpParameterBinding
{
   public GenericContractBinding(HttpParameterDescriptor descriptor) : base(descriptor){}
   public override Task ExecuteBindingAsync(ModelMetadataProvider provider, HttpActionContext context, CancellationToken cancellationToken)
   {
      if(context.ControllerContext.Configuration.DependencyResolver != null)
      {
         //This is a naive eval based only on the incoming type. You'll likely want to map
         var bazinga = context.Request.GetDependencyScope().GetService(Descriptor.ParameterType);
         if(bazinga.GetType() == typeof(GenericContract)
            context.ActionArguments[Descriptor.ParameterName] = bazinga;
      }
   }
}

Alternatively, you could also create a custom Web API ModelBinder and decorate your type with a ModelBinderAttribute (or declare the binder provider explicitly on the route method)

public class DecoupledTypeModelBinder : IModelBinder
{
   public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
   {

      //I'm assuming your JSON is being sent in the content of the request
      string content = actionContext.Request.Content.ReadAsStringAsync().Result;
      var json = JArray.Parse(content); //you really should switch to "array by default" processing;the number of items in it is an implementation detail.

      //In my opinion, this "ContractTypeService" gets injected at CompositionRoot, which would be in the IHttpControllerActivator
      var contractType = actionContext.RequestContext.Configuration.DependencyResolver.GetService(typeof(ContractTypeService)).GetAmbientContractType();

      List<GenericContract> contracts = new List<GenericContract>();
      foreach(var item in json.Children<JObject>())
      {    
           var contract = new GenericContract();
           contract.ContractValue = (double)item["ContractValue"].Value<JToken>();
           contract.ContractType = contractType;
           contracts.Add(contract);
         }
      }

      //YMMV; You could enforce a hard requirement here for singularity or do something else if multiples are inbound on the wire
      DecoupledType model = new DecoupledType()
      {
         Contract = contracts.Single()
      };

      bindingContext.Model = model;
   }
}

public class DecoupledTypeModelBinderProvider : ModelBinderProvider
{
   public override IModelBinder GetBinder(System.Web.Http.HttpConfiguration configuration, Type modelType)
   {
      return new DecoupledTypeModelBinder();
   }
}
...(in your controller)
public dynamic Post([ModelBinder(typeof(DecoupledTypeModelBinderProvider))]DecoupledType bazinga)
{
    var contract = bazinga.Contract;
    var contractType = contract.ContractType;
    var contractValue = contract.ContractValue;
}

Hope this sets you on a track towards success.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM