Complex Json object singe and array of objects model creation with entity framework

labjac

Member
Joined
May 26, 2022
Messages
9
Programming Experience
1-3
Hallo

We got the following two messages that is posted to API. but we need to be able to process both option in the same model.

I have been trying to do this by override on the Read and Write JasonConverter but it's either going to trigger on TSCREQ object or List<TSCREQ> it needs to be able to resolve both option in the same model

Object with single segment:
{
  "WCSTSCORD0100": {
    "HEAD": {
      "Message_Id": "OUT21220118021156653304000109729",
      "Date_Time": 20220118141157,
      "Message_Type": "WCSTSCORD",
      "Message_Version": 100,
      "Sender_Id": "WMS_RP03",
      "Recipient_Id": "658AICS1",
      "TSCREQ":
        {
          "Automation_Id": "658AICS1",
          "Site_Id": "ZAJNB02",
          "Client_Id": "ZAGDE",
          "New_Priority": 4700,
          "TSCDET": {
            "Key": 6861686
          }   
        } 
    }
  }
}

Object with array segment:
{
  "WCSTSCORD0100": {
    "HEAD": {
      "Message_Id": "OUT21220118021156653304000109729",
      "Date_Time": 20220118141157,
      "Message_Type": "WCSTSCORD",
      "Message_Version": 100,
      "Sender_Id": "WMS_RP03",
      "Recipient_Id": "658AICS1",
      "TSCREQ": [
        {
          "Automation_Id": "658AICS1",
          "Site_Id": "ZAJNB02",
          "Client_Id": "ZAGDE",
          "New_Priority": 4700,
          "TSCDET": {
            "Key": 6861686
          }
        },
        {
          "Automation_Id": "658AICS1",
          "Site_Id": "ZAJNB02",
          "Client_Id": "ZAGDE",
          "New_Priority": 4700,
          "TSCDET": {
            "Key": 6861687
          }
        },
        {
          "Automation_Id": "658AICS1",
          "Site_Id": "ZAJNB02",
          "Client_Id": "ZAGDE",
          "New_Priority": 4700,
          "TSCDET": {
            "Key": 6861688
          }
        }
      ]
    }
  }
}

The models create for this as follow:

Models been created:
  public class DSVWCSTSCORD0100_Inbound
    {
        public int Id { get; set; }
        public DSVWCSTSCORD0100? DSV_WCSTSCORD0100 { get; set; }
    }
    public class DSVWCSTSCORD0100
    {
        public int Id { get; set; }
        public HEAD_WCSTSCORD? HEAD { get; set; }
    }

    public class HEAD_WCSTSCORD : HEAD
    {    
        public List<TSCREQ>? TSCREQ { get; set; }
    }
    public class TSCDET
    {
        public int Id{ get; set; }
        public int Key { get; set; }
    }

    public class TSCREQ
    {
        public int Id { get; set; }
        public string? Automation_Id { get; set; }
        public string? Site_Id { get; set; }
        public string? Client_Id { get; set; }
        public int New_Priority { get; set; }
        public TSCDET? TSCDET { get; set; }
    }
}

The issue is the model allows for the list of TSCREQ object, but doesn't resolve the single point issue. (Code below will do but this needs to be catered for both options.


Class that will cater for the single object:
public class HEAD_WCSTSCORD : HEAD
    {    
        public TSCREQ? TSCREQ { get; set; }
    }
 
   public class TSCREQ
    {
        public int Id { get; set; }
        public string? Automation_Id { get; set; }
        public string? Site_Id { get; set; }
        public string? Client_Id { get; set; }
        public int New_Priority { get; set; }
        public TSCDET? TSCDET { get; set; }
    }
 
Last edited by a moderator:
Solution
The way I would tackle it is create something like:
C#:
public class DSVWCSTSCORD0100_Inbound
{
    public int Id { get; set; }
    public DSVWCSTSCORD0100? DSV_WCSTSCORD0100 { get; set; }
}

public class DSVWCSTSCORD0100
{
    public int Id { get; set; }
    public HEAD? HEAD { get; set; }  // <<< Just import the base class here
}

:

and deserialize the JSON directly into this. That should get you everything except for the TSCREQ field. But since HEAD_WCSTSCORD derives from HEAD, you can instantiate that later. Populate it with the values of the what was originally deserialized (using Automapper if you don't want the drudgery) of copying fields one at time.

So now the problem is just detecting whether the...
I recommend using the List<TSCREQ> and just writing a custom deserializer for your API. As for serializing. always use the array format even if there is a single item in the array.
 
Hallo

Problem is the custom deserializer will not trigger on the attribute if a single object is passed.

Code that will not trigger:
 public class HEAD_WCSTSCORD : HEAD
    {       
        [JsonConverter(typeof(ModelToListObjectConverterNotNewtonSoft))]
        public List<TSCREQ>? TSCREQ { get; set; }
    }
Sample code that will not trigger:
{
  "WCSTSCORD0100": {
    "HEAD": {
      "Message_Id": "OUT21220118021156653304000109729",
      "Date_Time": 20220118141157,
      "Message_Type": "WCSTSCORD",
      "Message_Version": 100,
      "Sender_Id": "WMS_RP03",
      "Recipient_Id": "658AICS1",
      "TSCREQ": {
        "Automation_Id": "658AICS1",
        "Site_Id": "ZAJNB02",
        "Client_Id": "ZAGDE",
        "New_Priority": 4700,
        "TSCDET": {
          "Key": 6861686
        }
      }
    }
  }
}

It will only trigger if the TSCREQ is a List<TSCREQ>. ignore the non implementation but the breakpoint is only hit when a List is passed.

Custom not triggering:
 public class ModelToListObjectConverterNotNewtonSoft : JsonConverter<List<TSCREQ>>
    {
        public override List<TSCREQ>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }

        public override void Write(Utf8JsonWriter writer, List<TSCREQ> value, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    }
 
By custom deserializer, I mean, not depend on the JSON.NET deserialization infrastructure to deserialize into your domain objects. I mean use generic JToken and JObject to get the fragments of JSON and then use that to create your object graph.
 
Ooch

This was just a sample Json file, to convert all the JToken and JObjects will be a nightmare the files is large.. Or is there a way to only get the JToken from the object to trigger a attribute. or must a customize the entire Json File?
 
The way I would tackle it is create something like:
C#:
public class DSVWCSTSCORD0100_Inbound
{
    public int Id { get; set; }
    public DSVWCSTSCORD0100? DSV_WCSTSCORD0100 { get; set; }
}

public class DSVWCSTSCORD0100
{
    public int Id { get; set; }
    public HEAD? HEAD { get; set; }  // <<< Just import the base class here
}

:

and deserialize the JSON directly into this. That should get you everything except for the TSCREQ field. But since HEAD_WCSTSCORD derives from HEAD, you can instantiate that later. Populate it with the values of the what was originally deserialized (using Automapper if you don't want the drudgery) of copying fields one at time.

So now the problem is just detecting whether the "TSCREQ" field in the JSON is an array or an object. I believe that you can use JSON.NET's LINQ for JSON to probe what type of object that field is. Depending on what kind of field it is, you can either deserialize it as as a single object and add it into a new instance of List<TSCREQ>, or if it is a list, deserialize it directly as a List<TSCREQ> and assign the list instance.
 
The way I would tackle it is create something like:
C#:
public class DSVWCSTSCORD0100_Inbound
{
    public int Id { get; set; }
    public DSVWCSTSCORD0100? DSV_WCSTSCORD0100 { get; set; }
}

public class DSVWCSTSCORD0100
{
    public int Id { get; set; }
    public HEAD? HEAD { get; set; }  // <<< Just import the base class here
}

:

and deserialize the JSON directly into this. That should get you everything except for the TSCREQ field. But since HEAD_WCSTSCORD derives from HEAD, you can instantiate that later. Populate it with the values of the what was originally deserialized (using Automapper if you don't want the drudgery) of copying fields one at time.

So now the problem is just detecting whether the "TSCREQ" field in the JSON is an array or an object. I believe that you can use JSON.NET's LINQ for JSON to probe what type of object that field is. Depending on what kind of field it is, you can either deserialize it as as a single object and add it into a new instance of List<TSCREQ>, or if it is a list, deserialize it directly as a List<TSCREQ> and assign the list instance.

Thank Skydiver

It helped to be pushed into a direction, this below is what i've done, and it works well. Thanks again for the poitner.

Solution provided:
 public class WcsTsCord0100_Parser
    {
        public static DSVWCSTSCORD0100_Inbound DSVWCSTSCORD0100ToModel(JsonObject Json)
        {
            JsonElement root = JsonDocument.Parse(Json.ToString()).RootElement;

            var headElement = root
                .GetProperty("DSV_WCSTSCORD0100")
                .GetProperty("HEAD");

            var dsvWcsTscord0100_Model = new DSVWCSTSCORD0100_Inbound()
            {
                DSV_WCSTSCORD0100 = new DSVWCSTSCORD0100()
                {
                    HEAD = new HEAD_WCSTSCORD()
                    {
                        Message_Id = headElement.TryGetProperty("Message_Id", out var messageId) ? messageId.GetString() : null,
                        Date_Time = headElement.TryGetProperty("Date_Time", out var dateTime) ? dateTime.GetString() : null,
                        Message_Type = headElement.TryGetProperty("Message_Type", out var messageType) ? messageType.GetString() : null,
                        Message_Version = headElement.TryGetProperty("Message_Version", out var messageVersion) ? messageVersion.GetString() : null,
                        Sender_Id = headElement.TryGetProperty("Sender_Id", out var senderId) ? senderId.GetString() : null,
                        Recipient_Id = headElement.TryGetProperty("Recipient_Id", out var recipientId) ? recipientId.GetString() : null,
                        TSCREQ = TSCREQToModel(headElement.GetProperty("TSCREQ"))
                    }
                }
            };
            return dsvWcsTscord0100_Model;
        }

        static List<TSCREQ_WCSTSCORD> TSCREQToModel(JsonElement jsonElement)
        {         
            var tscReqsList = new List<TSCREQ_WCSTSCORD>();

            if(jsonElement.ValueKind== JsonValueKind.Object)           
            {
                var tscReqsElement = new TSCREQ_WCSTSCORD()
                {
                    Automation_Id = jsonElement.TryGetProperty("Automation_Id", out JsonElement automationId) ? automationId.GetString() : null,
                    Site_Id = jsonElement.TryGetProperty("Site_Id", out JsonElement siteId) ? siteId.GetString() : null,
                    Client_Id = jsonElement.TryGetProperty("Client_Id", out JsonElement clientId) ? clientId.GetString() : null,
                    Reason_Code = jsonElement.TryGetProperty("Reason_Code", out JsonElement reasonCode) ? reasonCode.GetString() : null,
                    New_Status = jsonElement.TryGetProperty("New_Status", out JsonElement newStatus) ? newStatus.GetString() : null,
                    New_Priority = jsonElement.TryGetProperty("New_Priority", out JsonElement newPriority) ? newPriority.GetString() : null,
                    TSCDET = TscDetObjecttoArray(jsonElement.GetProperty("TSCDET"))
                };
                tscReqsList.Add(tscReqsElement);               
            }
            else if (jsonElement.ValueKind == JsonValueKind.Array)
            {
                foreach(var jsonElementItem in jsonElement.EnumerateArray())
                {
                    var tscReqsElement = new TSCREQ_WCSTSCORD()
                    {
                        Automation_Id = jsonElementItem.TryGetProperty("Automation_Id", out JsonElement automationId) ? automationId.GetString() : null,
                        Site_Id = jsonElementItem.TryGetProperty("Site_Id", out JsonElement siteId) ? siteId.GetString() : null,
                        Client_Id = jsonElementItem.TryGetProperty("Client_Id", out JsonElement clientId) ? clientId.GetString() : null,
                        Reason_Code = jsonElementItem.TryGetProperty("Reason_Code", out JsonElement reasonCode) ? reasonCode.GetString() : null,
                        New_Status = jsonElementItem.TryGetProperty("New_Status", out JsonElement newStatus) ? newStatus.GetString() : null,
                        New_Priority = jsonElementItem.TryGetProperty("New_Priority", out JsonElement newPriority) ? newPriority.GetString() : null,
                        TSCDET = TscDetObjecttoArray(jsonElementItem.GetProperty("TSCDET"))
                    };
                    tscReqsList.Add(tscReqsElement);
                }
            }
            return tscReqsList;
        }

        private static List<TSCDET_WCSTSCORD> TscDetObjecttoArray(JsonElement jsonElement)
        {
           var tscDets = new List<TSCDET_WCSTSCORD>();

            if(jsonElement.ValueKind == JsonValueKind.Object)             
            {
                var tscobj = new TSCDET_WCSTSCORD()
                {
                    Key = jsonElement.TryGetProperty("Key", out JsonElement key) ? key.GetString() : null,
                    List_Id = jsonElement.TryGetProperty("Key", out JsonElement listId) ? listId.GetString() : null
                };
                tscDets.Add(tscobj);           
            }
            else if(jsonElement.ValueKind == JsonValueKind.Array)
            {
                foreach (var jsonElementItem in jsonElement.EnumerateArray())
                {
                    var tscobj = new TSCDET_WCSTSCORD()
                    {
                        Key = jsonElementItem.TryGetProperty("Key", out JsonElement key) ? key.GetString() : null,
                        List_Id = jsonElementItem.TryGetProperty("Key", out JsonElement listId) ? listId.GetString() : null
                    };
                    tscDets.Add(tscobj);
                }
            }
            return tscDets;
        }
    }
 
Solution
As an aside, the two main Json serializer libraries both support attributes ([JsonProperty] in Newtonsoft and [JsonPropertyName] in STJ) that allow you to declare the name in the json so you don't have to violate C# naming conventions, twisting C# property names to match the json

Do this:

C#:
public class  SomeNicerNameHereInbound
    {
        public int Id { get; set; }

        [JsonProperty("DSV_WCSTSCORD0100")]
        public SomeNicerClassNameHere? SomeNicerPropertyNameHere { get; set; }
    }

    ...
    [JsonProperty("HEAD")]
        public Head? Head { get; set; }
    }

    
    ...
    [JsonProperty("Automation_Id")]
        public string? AutomationId { get; set; }
    }

In preference to making property names like DSV_WCSTSCORD0100 or HEAD or Automation_Id etc
 
Back
Top Bottom