Nested list of maps add or update another nested key

I have a nested list that should follow a rules to update a record or add a record.
I have a keys list which is the path I need to update or add

Entry list

[
      %{
        name: :actor,
        opts: [
          struct: MishkaDeveloperToolsTest.GuardedStruct.NestedConditionalFieldTest.Actor,
          derive: "validate(map, not_empty)",
          hint: "001-1-map",
          validator: {ConditionalFieldValidatorTestValidators, :is_map_data},
          __tag__: "root"
        ]
      },
      %{
        name: :actor,
        opts: [
          derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
          hint: "001-2-url",
          validator: {ConditionalFieldValidatorTestValidators, :is_string_data},
          __tag__: "root"
        ]
      }
    ]

For example I have a keys list like this ["root", "aa"], and I want to add new record to the list

  1. I need to find is there a list that has __tag__: "root::aa" if not add another map to bottom of the list
  2. if it can be found, I need add a map inside fields key

When can not find

[
      %{
        name: :actor,
        opts: [
          struct: MishkaDeveloperToolsTest.GuardedStruct.NestedConditionalFieldTest.Actor,
          derive: "validate(map, not_empty)",
          hint: "001-1-map",
          validator: {ConditionalFieldValidatorTestValidators, :is_map_data},
          __tag__: "root"
        ]
      },
      %{
        name: :actor,
        opts: [
          derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
          hint: "001-2-url",
          validator: {ConditionalFieldValidatorTestValidators, :is_string_data},
          __tag__: "root"
        ]
      },
     // add new map last of the list
     %{
        name: :actor,
        opts: [
          derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
          hint: "001-2-url",
          validator: {ConditionalFieldValidatorTestValidators, :is_string_data},
          __tag__: "root:aa"
        ]
      }
    ]

When we find it

[
  %{
    name: :actor,
    opts: [
      struct: MishkaDeveloperToolsTest.GuardedStruct.NestedConditionalFieldTest.Actor,
      derive: "validate(map, not_empty)",
      hint: "001-1-map",
      validator: {ConditionalFieldValidatorTestValidators, :is_map_data},
      __tag__: "root"
    ]
  },
  %{
    name: :actor,
    opts: [
      derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
      hint: "001-2-url",
      validator: {ConditionalFieldValidatorTestValidators, :is_string_data},
      __tag__: "root"
    ]
  },
  %{
    name: :actor,
    opts: [
      derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
      hint: "001-2-url",
      validator: {ConditionalFieldValidatorTestValidators, :is_string_data},
      __tag__: "root:aa"
    ],
    // add the map inside fields
    fields: [
      %{
        name: :actor,
        opts: [
          derive: "sanitize(tag=strip_tags) validate(url, max_len=160)",
          hint: "001-2-url",
          validator: {ConditionalFieldValidatorTestValidators, :is_string_data},
          __tag__: "root:aa"
        ]
      }
    ]
  }
]

As you see I added it inside the last map and add fields keys.


It should be noted the path keys can have 3 or 4 etc items and my code needs to implement none-limit nested map. for example:

 __tag__: "root:aa::bb::cc::gg"
["root", "aa", "bb", "cc", "gg"]

Thank you very much for your guidance

1 Like
defmodule E do
  def foo(record, keys, list) do
    key = Enum.join(keys, "::")

    # a function that extracts the key from the record 
    record_key = fn record ->
      record.opts[:__tag__]
    end

    assign_key = fn record ->
      %{record | opts: Keyword.replace(record.opts, :__tag__, key)}
    end

    Enum.group_by(list, record_key)
    |> Map.update(key, record, fn existing_record ->
      existing_record[:fields] = [record | existing_record[:fields]]
    end)
    |> Map.values()
  end
end

You can use something like this. Probably its not 100% correct but the idea is there.

  1. construct the key from the list
  2. Convert the list to a map using the key you want to check if it exists
  3. Use Map.update/4 to update the record with the given key. If it exists, you push the record to the fields field. If not you assign the record as a value of the map
  4. Convert back to a list to get the same structure you had as input
  5. use assign_key to set the records tag to the join key constructed from the list.

Hi, I tried to convert all stuff to map and update with update_in, but I could not do this, and with your code and suggestion has error unfortunately.