2

I am working with .NET Core and building an API. I have incoming JSON which I'm mapping to C# objects, and for the most part the JSON is quite simple, except one section which potentially has an array within an array, see below:

"marketing_preferences": [
  {
    "channel": "EDM",
    "opt_in": false,
    "valid_from_date": "2020-05-30T07:07:53.723Z",
    "content_type_preferences": [
      {
        "type": "CAT",
        "opt_in": false,
        "valid_from_date": "2020-05-30T07:07:53.724Z"
      },
      {
        "type": "TAC",
        "opt_in": true,
        "valid_from_date": "2020-05-30T07:07:53.724Z"
      }
    ]
  },
  {
    "channel": "SMS",
    "opt_in": true,
    "valid_from_date": "2020-05-30T07:07:53.724Z",
    "content_type_preferences": []
  },
  {
    "channel": "SM",
    "opt_in": true,
    "valid_from_date": "2020-05-30T07:07:53.724Z",
    "content_type_preferences": []
  }
]

Marketing preferences may (but may not) contain one or more content_type_preferences. So marketing_preferences is an array, and content_type_preferences is an optional array within that array. Mapping or projecting marketing preferences within my code is simple enough:

    customer.MarketingPreferences = source.RequestCustomer.MarketingPreferences
        .Select(x => new MarketingPreferences()
        {
            ChannelId = x.Channel,
            OptIn = x.OptIn,
            ValidFromDate = x.ValidFromDate,
            UpdatedBy = Functions.DbUser,
            CreatedDate = DateTime.UtcNow,
            UpdatedDate = DateTime.UtcNow,
            UpdatingStore = null // Not passed by API
        })
        .ToList();

However I'm a bit stuck on the syntax to project the content_type_preferences onto their equivalent object. Here are the classes I'm trying to project to:

public partial class MarketingPreferences
{
    public MarketingPreferences()
    {
        ContentTypePreferences = new HashSet<ContentTypePreferences>();
    }

    public Guid CustomerInternalId { get; set; }
    public string ChannelId { get; set; }
    public bool? OptIn { get; set; }
    public DateTime? ValidFromDate { get; set; }
    public string UpdatedBy { get; set; }
    public DateTime? CreatedDate { get; set; }
    public DateTime? UpdatedDate { get; set; }
    public string UpdatingStore { get; set; }

    public virtual Customer CustomerInternal { get; set; }
    public virtual ICollection<ContentTypePreferences> ContentTypePreferences { get; set; }
}

and

public partial class ContentTypePreferences
{
    public Guid CustomerInternalId { get; set; }
    public string ChannelId { get; set; }
    public string TypeId { get; set; }
    public bool? OptIn { get; set; }
    public DateTime? CreatedDate { get; set; }
    public DateTime? UpdatedDate { get; set; }
    public DateTime? ValidFromDate { get; set; }
    public string UpdatedBy { get; set; }
    public string UpdatingStore { get; set; }

    public virtual MarketingPreferences C { get; set; }
    public virtual Customer CustomerInternal { get; set; }
}

I did start to revert to a clunky way of doing it, but I'm really not happy with it and would prefer to try and find the LINQ-ish way to achieve the below:

    foreach (var marketingPrefs in source.RequestCustomer.MarketingPreferences)
    {
        if (marketingPrefs.ContentTypePreferences.Length > 0)
        {
            Console.WriteLine($"marketing preference: {marketingPrefs.Channel}");
            foreach (var contentPrefs in marketingPrefs.ContentTypePreferences)
            {
                Console.WriteLine($"content preference: {contentPrefs.Type}");
                customer.ContentTypePreferences.Add(new ContentTypePreferences
                {
                    TypeId = contentPrefs.Type,
                    OptIn = contentPrefs.OptIn,
                    ValidFromDate = contentPrefs.ValidFromDate
                });
            }
        }
    }
3
  • 2
    Would SelectMany work? Commented Jun 4, 2020 at 23:39
  • Hi mjwills, could you expand perhaps with an example of what you mean? Commented Jun 4, 2020 at 23:42
  • Does this answer your question? Flatten an Array in C# Commented Jun 5, 2020 at 9:25

2 Answers 2

1

Simple SelectMany should be enough to flatten the collection:

customer.ContentTypePreferences = source.RequestCustomer.MarketingPreferences
    .SelectMany(mp => mp.ContentTypePreferences)
    .Select(contentPrefs => new ContentTypePreferences
            {
                TypeId = contentPrefs.Type,
                OptIn = contentPrefs.OptIn,
                ValidFromDate = contentPrefs.ValidFromDate
            })
    .ToList();
Sign up to request clarification or add additional context in comments.

Comments

1

I did find a way to solve this issue using the below.

    customer.MarketingPreferences = source.RequestCustomer.MarketingPreferences
        .Select(x => new MarketingPreferences()
        {
            ChannelId = x.Channel,
            OptIn = x.OptIn,
            ValidFromDate = x.ValidFromDate,
            UpdatedBy = Functions.DbUser,
            CreatedDate = DateTime.UtcNow,
            UpdatedDate = DateTime.UtcNow,
            ContentTypePreferences = (from c in x.ContentTypePreferences
                    select new ContentTypePreferences
                    {
                        TypeId = c.Type,
                        OptIn = c.OptIn,
                        ValidFromDate = c.ValidFromDate,
                        ChannelId = x.Channel // Should inherit parent marketing preference channel
                    }).ToList(),
            UpdatingStore = null // Not passed by API
        })
        .ToList();

I appreciate the other answers, but realised that just flattening might not be enough, as the ChannelId for each ContentTypePreference isn't passed in with the POST JSON but is instead derived from the parent MarketingTypePreference for that ContentTypePreference (e.g. a child of EMAIL will have a channel Id of EMAIL) so the above solution lets me inject that when creating each new ContentTypePreferences for Entity Framework.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.