Converting Union to Original Data

Hi, all

Is there any builtin function to convert a union from {type: :type_name, value: the_value} to the original data it was created from?

Or are there helper functions that will be useful if it can only be done manually?

There is no way to necessarily know the original value. i.e %Union{type: :string, value: "10"} could have been created from 10 or "10". What are you trying to do?

Ultimately, I’m trying to achieve a flow like this:

ResourceWithNestedUnions.create!(...)
|> encode_to_json()
|> decode_from_json()
|> ResourceWithNestedUnions.create!()

One challenge comes from Ash.Union implementing Jason.Encoder to return only the value.

I’ve tried checking how dump_to_native and cast_stored for Ash.Type.Union could be useful. No real progress.

Not all types have tag and tag_value configured.

There is no guarantee that the json representation of any given resource can be fed back into create one.

Why are you trying to encode/decode like that? I only ask to make sure that we’re avoiding the X/Y problem here.

If you must, you could try something like this instead:

record = ResourceWithNestedUnions.create!(...)

attributes = Ash.Resource.Info.attributes(record)

dumped = 
  Enum.reduce(attributes, %{}, fn attribute, acc -> 
    value = Map.get(record, attribute.name)
    {:ok, dumped_value} = Ash.Type.dump_to_embedded(attribute.type, value, attribute.constraints)

    Map.put(acc, attribute.name, dumped_value)
  end)

dumped
|> encode_to_json()
|> decode_from_json()
|> # reverse the above process for each key in the json, using `cast_stored`.
|> ResourceWithNestedUnions.create!()

That would have a “higher chance” of being able to be fed into a create action successfully.

This helps a lot. I thought not to distract with much context.

I’m working with an event-sourced (commanded) app and we’re using Ash resources in place of structs for events. With our setup of the library’s event store, all events are serialized for storage (and deserialized) using Jason.

With unions' default Jason.Encoder implementation of discarding the type, the stored event vs the original event resource can become irreconcilable.

Since there’s no simple plug-and-play data layer that takes this problem away (ash_commanded tries to be one), I had to find a way to store richer data in the event that can then be deserialized later.

My present solution is to create a dumped_data attribute and a data calculation that (normalizes and then) casts the stored dumped_data.

Thanks a ton, Zach. I’m open to any other ideas you might have now that you know the context better.

Yeah, I’d suggest going for a “dump” and “cast_stored” strategy, instead of a Jason encoder/decoder for that case. Well, you’d encode after dumping all of the fields recursively, and you’d decode before casting stored, but that process is how it’s intended to work.