Home > Back-end >  Order list of objects depending on it's nested list of objects
Order list of objects depending on it's nested list of objects

Time:07-06

I have a list of objects that I want to sort with multiple conditions, I was able to do it with the first two ones, but with the last condition, I'm having a hard time finding a way to achieve it.

I want to order the list of objects first by gamme and after that by series and finally by the list of characteristics depending on a certain order that I know in advance.

In this case, I want the last sort will respect the following order, this list is dynamic so the order may change:

["largeur","longueur","epaisseur","type_pose"] by value of course.

I'm stuck right now with this:

listOfProducts.OrderBy(c => c.gamme).ThenBy(c => c.serie); 
[
  {
    "uid": "QB32-20220621-10096",
    "gamme": "ATENEA",
    "serie": "ALASKA",
    "caracteristiques": [
      {
        "nom": "type_pose",
        "label": null,
        "valeur": "Pose collée ou scellée selon les dispositions des textes de mise en œuvre"
      },
      {
        "nom": "type_carreau",
        "label": null,
        "valeur": "Grès pressé émaillé"
      },
      {
        "nom": "groupe_absorption",
        "label": null,
        "valeur": "Bla"
      },
      {
        "nom": "usage exterieur",
        "label": null,
        "valeur": "Conforme"
      },
      {
        "nom": "glissance",
        "label": null,
        "valeur": "Non revendiqué"
      },
      {
        "nom": "finition",
        "label": null,
        "valeur": "Lisses"
      },
      {
        "nom": "largeur",
        "label": null,
        "valeur": "500"
      },
      {
        "nom": "longueur",
        "label": null,
        "valeur": "500"
      },
      {
        "nom": "epaisseur",
        "label": null,
        "valeur": "9"
      },
      {
        "nom": "particularite",
        "label": null,
        "valeur": "Non rectifié"
      },
      {
        "nom": "part_produit",
        "label": null,
        "valeur": "nc"
      },
      {
        "nom": "ref_commerciale_f",
        "label": null,
        "valeur": ""
      },
      {
        "nom": "coloris_f",
        "label": null,
        "valeur": "Blanco"
      },
      {
        "nom": "option",
        "label": null,
        "valeur": "/"
      },
      {
        "nom": "classement",
        "label": null,
        "valeur": "U3 P3 E3 C2"
      }
    ]
  },
  {
    "uid": "QB32-20220621-10177",
    "gamme": "ATENEA",
    "serie": "ALOE",
    "caracteristiques": [
      {
        "nom": "type_pose",
        "label": null,
        "valeur": "Pose collée ou scellée selon les dispositions des textes de mise en œuvre"
      },
      {
        "nom": "type_carreau",
        "label": null,
        "valeur": "Grès pressé non émaillé"
      },
      {
        "nom": "groupe_absorption",
        "label": null,
        "valeur": "Bla"
      },
      {
        "nom": "usage exterieur",
        "label": null,
        "valeur": "Conforme"
      },
      {
        "nom": "glissance",
        "label": null,
        "valeur": "Non revendiqué"
      },
      {
        "nom": "finition",
        "label": null,
        "valeur": "Décorés"
      },
      {
        "nom": "largeur",
        "label": null,
        "valeur": "607.5"
      },
      {
        "nom": "longueur",
        "label": null,
        "valeur": "607.5"
      },
      {
        "nom": "epaisseur",
        "label": null,
        "valeur": "9.6"
      },
      {
        "nom": "particularite",
        "label": null,
        "valeur": "Non rectifié"
      },
      {
        "nom": "part_produit",
        "label": null,
        "valeur": "nc"
      },
      {
        "nom": "ref_commerciale_f",
        "label": null,
        "valeur": ""
      },
      {
        "nom": "coloris_f",
        "label": null,
        "valeur": "Taupe"
      },
      {
        "nom": "option",
        "label": null,
        "valeur": "/"
      },
      {
        "nom": "classement",
        "label": null,
        "valeur": "U4 P4 E3 C2"
      }
    ]
  },
  {
    "uid": "QB32-20220621-10054",
    "gamme": "CASA INFINITA",
    "serie": "IN TIME LAPPATO",
    "caracteristiques": [
      {
        "nom": "type_pose",
        "label": null,
        "valeur": "Pose collée ou scellée selon les dispositions des textes de mise en œuvre"
      }
    ]
  }
]

CodePudding user response:

You can sort by values in the sub-array like this:

listOfProducts
  .OrderBy(c => c.gamme)
  .ThenBy(c => c.serie)
  .ThenBy(c => c.caracteristiques
                 .FirstOrDefault(x => x.nom == "largeur")?.valeur)
  .ThenBy(c => c.caracteristiques
                 .FirstOrDefault(x => x.nom == "longueur")?.valeur)
  .ThenBy(c => c.caracteristiques
                 .FirstOrDefault(x => x.nom == "epaisseur")?.valeur)
  .ThenBy(c => c.caracteristiques
                 .FirstOrDefault(x => x.nom == "type_pose")?.valeur); 

In case of a dynamic list of attributes, you can build the Linq query dynamically:

var dynamicCriteria = new string[] { "largeur","longueur","epaisseur","type_pose" }; 
var sorted = listOfProducts
  .OrderBy(c => c.gamme)
  .ThenBy(c => c.serie);
foreach (var crit in dynamicCriteria) 
{
  sorted = sorted.ThenBy(c => c.caracteristiques
                 .FirstOrDefault(x => x.nom == crit)?.valeur);
}

Above code first searches the sub-array for the item with the specific name and then returns the value of the item for sort-comparison.

Downside of this Linq-based approach is that the sub-array is searched multiple times. If you need the comparison on multiple occasions and have a lot of caracteristiques, you could implement a custom comparer using the IComparer<T> interface or make the class comparable by using the IComparable<T> interface. In the Compare or CompareTo methods you can implement the comparison in a more efficient manner. The following sample shows a simple comparer (adjust it to your needs):

public class MyComparer : IComparer<Item> 
{
    private readonly IEnumerable<string> _dynamicCriteria; 
    
    public MyComparer(IEnumerable<string> dynamicCriteria) 
    {
        _dynamicCriteria = dynamicCriteria;
    }
    
    public int Compare(Item a, Item b) 
    {
        // Check static criteria
        var result = string.Compare(a.gamme, b.gamme);
        if (result != 0)
            return result;
        result = string.Compare(a.serie, b.serie);
        if (result != 0)
            return result;
        // Get values for dynamic criteria
        var dynamicValuesA = a.caracteristiques
            .Where(x => _dynamicCriteria.Contains(x.nom))
            .ToDictionary(x => x.nom, x => x.valeur);
        var dynamicValuesB = b.caracteristiques
            .Where(x => _dynamicCriteria.Contains(x.nom))
            .ToDictionary(x => x.nom, x => x.valeur);
        // Compare dynamic criteria
        foreach (var crit in _dynamicCriteria) 
        {
            string valueA;
            if (!dynamicValuesA.TryGetValue(crit, out valueA))
                valueA = string.Empty;
            string valueB;
            if (!dynamicValuesB.TryGetValue(crit, out valueB))
                valueB = string.Empty;
            result = string.Compare(valueA, valueB);
            if (result != 0)
                return result;
        }
        // No difference in dynamic criteria, items are equal
        return 0;
    }
}

You can use this comparer like this:

var dynamicCriteria = new string[] { "largeur","longueur","epaisseur","type_pose" }; 
var sorted = listOfProducts
  OrderBy(c => c, new MyComparer(dynamicCriteria));
  • Related