Search code examples
c#json.nettype-conversionjson-deserialization

Json complex deserialization of object to model class


I receive a json string from a Third Party. This json string needs to be deserialized into a model class, which is fine for most part. However, in this json string there is a property that cannot be defined as a specific model class as it depends on the object (contact, quote, pet item) being sent. This json string I receive cannot be changed and I'm not in control of how this is being sent through.

I have created a Model Converter (JsonConverter) to cast the object into a specific model, however checking if the object is for that specific model isn't working.

The main model looks as follows and the Properties needs to be cast to a different class model:

[DataContract]
public class WorkflowWebhookModel
{
    [DataMember(Name = "objectType")]
    public string ObjectType { get; set; }

    [DataMember(Name = "objectTypeId")]
    public string ObjectTypeId { get; set; }

    [DataMember(Name = "incobjectIdeption_date")]
    public long? ObjectId { get; set; }

    /// <summary>
    /// Depending on the object type (e.g. contact, deal, custom object) the property needs to be converted to the corresponding object type.
    /// </summary>
    [DataMember(Name = "properties")]
    [JsonConverter(typeof(ModelConverter))]
    public dynamic Properties { get; set; }
 }

The JsonConverted was created to convert the properties into the corresponding model class. Calling the .ToObject works fine if you cast it into the specific model class, but at this stage, this object is not specific - it can be any of 3 different model classes.

 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
       
        if (reader.TokenType == JsonToken.StartObject)
        {
            JToken token = JToken.Load(reader);
            
            if (token.Type == JTokenType.Object)
            {
                var model = token.ToObject<PetPropertiesModel>();

                var result = model as PetPropertiesModel; //This will always be null...
                                   

                //  return token.ToObject<ContactPropertiesModel>();
                //  return token.ToObject<DealPropertiesModel>();
                return token.ToObject<PetPropertiesModel>();
            }
        }

        return null;
    }

The 3 model classes contains different properties, for example:

Pet Properties:

[DataContract]
public class PetPropertiesModel
{
    [DataMember(Name = "discountpercentage")]
    public PropertyValuesModel DiscountPercentage { get; set; }

    [DataMember(Name = "gender")]
    public PropertyValuesModel Gender { get; set; }

    [DataMember(Name = "breed_code")]
    public PropertyValuesModel BreedCode { get; set; }

    ect }

The Deal model class will have properties containing:

 public class DealPropertiesModel
{
    [DataMember(Name = "dealname")]
    public PropertyValuesModel Dealname { get; set; }

    [DataMember(Name = "agent")]
    public PropertyValuesModel Agent { get; set; }

    [DataMember(Name = "quote_number")]
    public PropertyValuesModel QuoteNumber { get; set; }

 ect }

The contacts model class :

 [DataContract]
public class ContactPropertiesModel
{
    [DataMember(Name = "country")]
    public PropertyValuesModel Country { get; set; }

    [DataMember(Name = "marketing_opt_out")]
    public PropertyValuesModel MarketingOptOut { get; set; }

    [DataMember(Name = "firstname")]
    public PropertyValuesModel FirstName { get; set; }
 ect }

Calling the deserialization of the json string like this:

   var modelResult = JsonConvert.DeserializeObject<WorkflowWebhookModel>(payLoad);
   return modelResult;

From the JsonConverter, how can the object be checked to validate that it should be converted to a specific model class like the pet or deal or contact?

I've attempted in checking, but getting that the object is always of type object and cannot get it to validate against any of the 3 model classes to ensure it will be cast to the correct model class.

--------- UPDATE : Add json strings

Example of Pet json string:

 {"portalId":26971608,"objectType":"UNKNOWN","objectTypeId":"2-111693227","objectId":895899349,"properties":{"discountpercentage":{"versions":[{"name":"discountpercentage","value":"0","timestamp":1676887589843,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"aec1d9bd-4149-4348-8933-274a25a31dfe","useTimestampAsPersistenceTimestamp":true}],"value":"0","timestamp":1676887589843,"persistenceTimestamp":1676887589843,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null},"gender":{"versions":[{"name":"gender","value":"Female","timestamp":1676887589843,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"aec1d9bd-4149-4348-8933-274a25a31dfe","useTimestampAsPersistenceTimestamp":true}],"value":"Female","timestamp":1676887589843,"persistenceTimestamp":1676887589843,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null},"breed_code":{"versions":[{"name":"breed_code","value":"Retriver","timestamp":1676887589843,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"aec1d9bd-4149-4348-8933-274a25a31dfe","useTimestampAsPersistenceTimestamp":true}],"value":"Retriver","timestamp":1676887589843,"persistenceTimestamp":1676887589843,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null}},"version":1,"secondaryIdentifier":null,"isDeleted":false}

Example of Quote json string:

    {"portalId":26971608,"objectType":"DEAL","objectTypeId":"0-3","objectId":6737405630,"properties":{"dealname":{"versions":[{"name":"dealname","value":"5945677","timestamp":1677141507135,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"02f88354-2544-41d7-846a-f921342821e3","useTimestampAsPersistenceTimestamp":true}],"value":"5945677","timestamp":1677141507135,"persistenceTimestamp":1677141507135,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null},"agent":{"versions":[{"name":"agent","value":"TV","timestamp":1677141507135,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"02f88354-2544-41d7-846a-f921342821e3","useTimestampAsPersistenceTimestamp":true}],"value":"TV","timestamp":1677141507135,"persistenceTimestamp":1677141507135,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null},"createdate":{"versions":[{"name":"createdate","value":"1677141507135","timestamp":1677141507135,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"02f88354-2544-41d7-846a-f921342821e3","useTimestampAsPersistenceTimestamp":true}],"value":"1677141507135","timestamp":1677141507135,"persistenceTimestamp":1677141507135,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null},"payment_method":{"versions":[{"name":"payment_method","value":"Debit Order","timestamp":1677141507135,"sourceId":"1443150","source":"INTEGRATION","sourceVid":[],"requestId":"02f88354-2544-41d7-846a-f921342821e3","useTimestampAsPersistenceTimestamp":true}],"value":"Debit Order","timestamp":1677141507135,"persistenceTimestamp":1677141507135,"source":"INTEGRATION","sourceId":"1443150","updatedByUserId":null}},"version":7,"secondaryIdentifier":null,"isDeleted":false}

example of contact json string:

    {"vid":151,"canonical-vid":151,"merged-vids":[],"portal-id":26971608,"is-contact":true,"properties":{"country":{"value":"New Zealand","versions":[{"value":"New Zealand","source-type":"INTEGRATION","source-id":"1443150","source-label":null,"updated-by-user-id":null,"timestamp":1676968902732,"selected":false}]},"createdate":{"value":"1676968902732","versions":[{"value":"1676968902732","source-type":"INTEGRATION","source-id":"1443150","source-label":null,"updated-by-user-id":null,"timestamp":1676968902732,"selected":false}]},"marketing_opt_out":{"value":"FALSE","versions":[{"value":"FALSE","source-type":"INTEGRATION","source-id":"1443150","source-label":null,"updated-by-user-id":null,"timestamp":1676968902732,"selected":false}]},"home_telephone":{"value":"62748089","versions":[{"value":"62748089","source-type":"INTEGRATION","source-id":"1443150","source-label":null,"updated-by-user-id":null,"timestamp":1676968902732,"selected":false}]},"mobilephone":{"value":"64273594022","versions":[{"value":"64273594022","source-type":"INTEGRATION","source-id":"1443150","source-label":null,"updated-by-user-id":null,"timestamp":1676968902732,"selected":false}]}},"form-submissions":[],"list-memberships":[{"static-list-id":1,"internal-list-id":2147483643,"timestamp":1678679155705,"vid":151,"is-member":true},{"static-list-id":2,"internal-list-id":2147483643,"timestamp":1678683550966,"vid":151,"is-member":true},{"static-list-id":7,"internal-list-id":2147483643,"timestamp":1679647561307,"vid":151,"is-member":true}],"identity-profiles":[{"vid":151,"is-deleted":false,"is-contact":false,"pointer-vid":0,"previous-vid":0,"linked-vids":[],"saved-at-timestamp":0,"deleted-changed-timestamp":0,"identities":[{"type":"EMAIL","value":"[email protected]","timestamp":1676968902741,"is-primary":true,"source":"UNSPECIFIED"},{"type":"LEAD_GUID","value":"b010ca63-3482-4ee0-95ff-9ea7d9897eaf","timestamp":1676968902805,"source":"UNSPECIFIED"}]}],"merge-audits":[]}

Note, I shortened the amount of properties listed in each as it reached over 40000 characters...


Solution

  • this is working for me

    WorkflowWebhookModel workflowWebhookModel = JsonConvert.DeserializeObject<WorkflowWebhookModel>(jsonQuote);
    
    
    public class PropertyConverter : JsonConverter
    {
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    
        public override PropertiesModel ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
    
            var jObject = JObject.Load(reader);
    
            if (jObject.Properties().Any(p => p.Name == "discountpercentage"))
                return jObject.ToObject<PetPropertiesModel>();
            if (jObject.Properties().Any(p => p.Name == "dealname"))
                return jObject.ToObject<DealPropertiesModel>();
    
            return null;
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(PropertiesModel);
        }
    }
    

    classes

    public partial class WorkflowWebhookModel
    {
        [JsonProperty("portalId")]
        public long PortalId { get; set; }
    
        [JsonProperty("objectType")]
        public string ObjectType { get; set; }
    
        [JsonProperty("objectTypeId")]
        public string ObjectTypeId { get; set; }
    
        [JsonProperty("objectId")]
        public long ObjectId { get; set; }
    
    
        [JsonProperty("version")]
        public long Version { get; set; }
    
        [JsonProperty("secondaryIdentifier")]
        public object SecondaryIdentifier { get; set; }
    
        [JsonProperty("isDeleted")]
        public bool IsDeleted { get; set; }
    
        [JsonProperty("properties")]
        [JsonConverter(typeof(PropertyConverter))]
        public PropertiesModel Properties { get; set; }
    }
    
    public abstract class PropertiesModel
    {
        public string PropertyModelName => this.GetType().ToString();
    }
    
    public partial class PetPropertiesModel : PropertiesModel
    {
        [JsonProperty("discountpercentage")]
        public PropertyValuesModel Discountpercentage { get; set; }
    
        [JsonProperty("gender")]
        public PropertyValuesModel Gender { get; set; }
    
        [JsonProperty("breed_code")]
        public PropertyValuesModel BreedCode { get; set; }
    }