JamesMatson
JamesMatson

Reputation: 2932

c# using Linq to project an array within an array onto a new array?

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
                });
            }
        }
    }

Upvotes: 2

Views: 336

Answers (2)

JamesMatson
JamesMatson

Reputation: 2932

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.

Upvotes: 1

Guru Stron
Guru Stron

Reputation: 143078

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();

Upvotes: 1

Related Questions