1

I'm getting this string as query result from my database:

"%Sample.Struct{list: [], total: \"0.00\", day: 6, id: \"8vfts6\"}"

Is there any way to convert this one back to map? I'm getting this error decoding it with poison

** (Poison.SyntaxError) Unexpected token: %
(poison) lib/poison/parser.ex:56: Poison.Parser.parse!/2
(poison) lib/poison.ex:83: Poison.decode!/2

I can't fix the way data is being added to database, i must find a proper way for a key/value route to easily retrive data from that. (this is just a sample for a more complex result)

Razinar
  • 727
  • 3
  • 13
  • 21
  • 3
    This is not JSON. You could use eval (Code.eval_string) but that's generally a bad idea. How are you generating this thing? Using `inspect`? The string returned by inspect does not necessarily contain valid Elixir syntax, though in this example it does. – Dogbert Nov 29 '17 at 16:48
  • If you want to serialize Elixir struct or map to save it in the database, the way better would be to use pair or `:erlang.term_to_binary` and `:erlang.binary_to_term`. In your example you need to `eval_string` it which, as @Dogbert said, is not a very good idea for the security reasons (and more). – Grych Nov 29 '17 at 17:06
  • So if you can't fix the way it is store in the database, you'll need to write a parser for your struct. – Grych Nov 29 '17 at 17:09

1 Answers1

2

As it was mentioned in comments, you should not use Code.eval_string. But, there is a way to safely convert your code to Elixir struct, using Code module:

ex(1)> encoded = "%Sample.Struct{list: [], total: \"0.00\", day: 6, id: \"8vfts6\"}"
"%Sample.Struct{list: [], total: \"0.00\", day: 6, id: \"8vfts6\"}"

First, get the AST from the string, but use the pattern matching to ensure it is a struct you are looking for ({:__aliases__, _, [:Sample, :Struct]}). All other (potentially malicious) code will fail this match:

iex(2)> {:ok, {:%, _, [{:__aliases__, _, [:Sample, :Struct]}, {:%{}, _, keymap}]} = ast} = Code.string_to_quoted(encoded)
{:ok,
 {:%, [line: 1],
  [{:__aliases__, [line: 1], [:Sample, :Struct]},
   {:%{}, [line: 1], [list: [], total: "0.00", day: 6, id: "8vfts6"]}]}}

Here you have the full ast for you struct, and the keymap. You may now be tempted to use eval_quoted with the AST, to get the struct you needed:

iex(3)> {struct, _} = Code.eval_quoted(ast)
{%Sample.Struct{day: 6, id: "8vfts6", list: [], total: "0.00"}, []}
iex(4)> struct
%Sample.Struct{day: 6, id: "8vfts6", list: [], total: "0.00"}

But it is still not safe! Someone may put a function causing side effect into the string, like "%Sample.Struct{list: IO.puts \"Something\"}", which will be executed during the evaluation. So you will need to check the keymap firsts, if it contain safe data.

Or you may just use keymap directly, without evaluating anyting:

iex(5)> struct(Sample.Struct, keymap)                                                                                    
%Sample.Struct{day: 6, id: "8vfts6", list: [], total: "0.00"}
Grych
  • 2,861
  • 13
  • 22
  • 1
    One thing to note is that if the code contains maps or tuples, simply passing the parsed AST will not be enough because their quoted representation are different than their unquoted ones, e.g. `%{a: {}}` becomes `{:%{}, [line: 1], [a: {:{}, [line: 1], []}]}`. – Dogbert Nov 30 '17 at 16:46
  • Sure. It's just an example on this particular case, in case he has a data other than this particular struct, he'll need to match it separately. – Grych Nov 30 '17 at 17:56