Search code examples
c#reflectionconsole-applicationdynamic-columns

How to dynamically handle and display varying numbers of columns (e.g., drink ingredients) in C#?


I’m working on a project that fetches drink details from an API (TheCocktailDB). Each drink can have up to 15 ingredients and corresponding measurements, but the actual number of ingredients varies by drink.

I want to display this data dynamically in a table format. My issues are:

i am having trouble to dynamically handle varying numbers of ingredients and measurements for each drink. and to transform the data so I can display it in a key-value format (e.g., Ingredient1 -> Vodka, Ingredient2 -> Orange Juice) without hardcoding column names.

I have a Drink class with properties like strIngredient1, strIngredient2, ..., strIngredient15.

I was advised to “transpose the data into key-value pairs” like (Ingredient1 -> Vodka) to handle the variable number of columns, but I can't figure out how to implement this in.

Here’s my Drink model:

public class Drink
{
    public string? strDrink { get; set; }
    public string? strIngredient1 { get; set; }
    public string? strIngredient2 { get; set; }
    // up to strIngredient15
    public string? strMeasure1 { get; set; }
    public string? strMeasure2 { get; set; }
    // up to strMeasure15
}

Here’s my current approach for fetching and displaying drinks:

foreach (var drink in _drinkDetails.drinks)
{
    Console.WriteLine($"Drink Name: {drink.strDrink}");
    // How do I dynamically iterate through the ingredients and measures here?
}

what i expected :

A way to dynamically process the ingredient and measurement properties without hardcoding strIngredient1 through strIngredient15.

what happened:
I’m unable to dynamically access or iterate through these properties, making it difficult to handle the varying number of ingredients and format the data for display.

This is the JSON structure i am working with P.S this is only a single sample

Any guidance or examples would be greatly appreciated!


Solution

  • I’m unable to dynamically access or iterate through these properties, making it difficult to handle the varying number of ingredients and format the data for display.

    Here's a trick for those kind of situations:

    public class Drink
    {
        [JsonProperty("strDrink")]
        public string? Drink { get; set; }
    
        private Dictionary<int, string?> _ingredients = new();
    
        [JsonProperty("strIngredient1")]
        public string? Ingredient1 { 
          get => _ingredients.TryGetValue(1, out var v) ? v : null; 
          set => _ingredients[1] = value; 
        }
    
        [JsonProperty("strIngredient2")]
        public string? Ingredient2 { 
          get => _ingredients.TryGetValue(2, out var v) ? v : null; 
          set => _ingredients[2] = value; 
        }
    
    
        // up to strIngredient15
    
        private Dictionary<int, string?> _measures = new();
        
        [JsonProperty("strMeasure1")]
        public string? Measure1 { 
          get => _measures.TryGetValue(1, out var v) ? v : null; 
          set => _measures[1] = value; 
        }
        
        [JsonProperty("strMeasure2")]
        public string? Measure2 { 
          get => _measures.TryGetValue(2, out var v) ? v : null; 
          set => _measures[2] = value; 
        }
       
        // up to strMeasure15
    }
    

    Few things going on here so let's unpack it:

    First off, don't let the data you're working with dictate the shape or appearance of your c# models; don't violate c# naming conventions (pascal case, no Hungarian notation) by using the json prop names directly - use the attributes provided by your serializer to declare the json name that should be mapped to this c# property name

    To the main point, instead of storing the data in a named variable we leverage the property logic to redirect the data into a dictionary with a specific key per property

    I've chosen dictionary but you could easily use an array if you want to do the math on storing ingredient 1 in slot 0 (or make the array one slot larger and don't use slot 0, or some other consistent logic).

    The primary point made is that by doing this redirect you end up with some enumerable collection of your data, that you can treat like:

    var ordered = someDictionary
      .Where(kvp => kvp.Value != null) //only this that have a value
      .OrderBy(kvp => kvp.Key) //dictionaries don't necessarily have an order; it is best to be explicit
    
    foreach(var kvp in ordered) ...
      //do something with the KeyValuePair kvp
    

    You could then print each of these out with a Console.Write padded out to 20 spaces wide to get a horizontal arrangement of data (columns), or WriteLine to do it vertically but only those that aren't null etc. You could add some logic to the property set that only sets the value if the provided value isn't null, which should mean the dictionary never contains null values (the type can be Dictionary<ibt, string>) and you don't need to do that filter every time

    If this is an academic exercise and you haven't been taught about dictionaries yet, consider the collections you have been taught about.. An array version might look like:

    private string?[] _ingredients = new string?[15];
    
    [JsonProperty("strIngredient1")]
    public string? Ingredient1 { 
      get => _ingredients[0]; 
      set => _ingredients[0] = value; 
    }
    
    
    ..
    
    for(int x = 0; x < _ingredients.Length; x++){
      var ing = _ingredients[x];
      if(ing == null) break;
      Console.WriteLine($"{x+1}: {ing}");
    }
    

    This does the "store ingredient 1 in slot 0, and add one later when formatting for display" logic. It's arguably simpler than dictionary but I wanted to show dictionary as it can take something other than an int for its key; one day you might want to associate eg a timestamp to an energy reading or something, and KeyValuePair being able to accept any type for its key and value is useful for things like that