user4672604
user4672604

Reputation:

how to create JSON string in Erlang manually

I am new to Erlang and noticed that there is no native function to create json string from lists (Or is there?). I use this method to create json string in Erlang but do not know if it will not malfunction.

Here is an example of my method:

-module(index).
-export([test/0]).

test() ->
    Ma = "Hello World", Mb = "Hello Erlang",
    A = "{\"Messages\" : [\"" ++ Ma ++ "\", \""++Mb++"\"], \"Usernames\" : [\"Username1\", \"Username2\"]}",
    A.

The result is:

388> test().
"{\"Messages\" : [\"Hello World\", \"Hello Erlang\"], \"Usernames\" : [\"Username1\", \"Username2\"]}"
389> 

I think this is the expected result but is there any chance that this method may malfunction when included special characters, such as: <, >, & / \ " ??

What precautions should I take to make this method stronger?

Upvotes: 3

Views: 2712

Answers (5)

Ukr
Ukr

Reputation: 2704

I'm using jsone library.

%% Encode
> jsone:encode([1,2,3]).
<<"[1,2,3]">>

> jsone:encode(#{<<"key">> => <<"value">>}).  % map format
> jsone:encode({[{<<"key">>, <<"value">>}]}). % tuple format
> jsone:encode([{<<"key">>, <<"value">>}]).  % proplist format
<<"{\"key\":\"value\"}">>

> jsone:encode(#{key => <<"value">>}). % atom key is allowed
<<"{\"key\":\"value\"}">>

Upvotes: 0

Joe Gasewicz
Joe Gasewicz

Reputation: 1485

I had this very same problem, searched high and low and in the end came up with my own method. This is purely just pointing people in the right directions to finding a solution for themselves. Note: I tried jiffy but as I'm using rebar3 it's not currently compatible.

Im using MS sql server so i use the Erlang odbc module: http://erlang.org/doc/man/odbc.html

The odbc:sql_query/2 gives me back {selected, Columns, Results} From here i can take the Columns which is a list of strings & the Results, a list of rows represented each as a tuple, then create a few functions to output valid Erlang code to be able to serialize correctly to Json based on a number of factors. Here's the full code:

make the initial query:

  Sql = "SELECT * FROM alloys;",
  Ref = connect(),
  case odbc:sql_query(Ref, Sql) of
    {selected, Columns, Results} ->
      set_json_from_sql(Columns, Results, []);
    {error, Reason} ->
      {error, Reason}
  end.

Then the input function is set_json_from_sql/3 that calls the below functions:

format_by_type(Item) ->                                                                          
  if                                                                                             
    is_list(Item) -> list_to_binary(io_lib:format("~s", [Item]));                                
    is_integer(Item) -> Item;                                                                    
    is_boolean(Item) -> io_lib:format("~a", [Item]);                                             
    is_atom(Item) -> Item                                                                        
  end.                                                                                           

json_by_type([H], [Hc], Data) ->                                                                 
  NewH = format_by_type(H),                                                                      
  set_json_flatten(Data, Hc, NewH);                                                              

json_by_type([H|T], [Hc|Tc], Data) ->                                                            
  NewH = format_by_type(H),                                                                      
  NewData = set_json_flatten(Data, Hc, NewH),                                                    
  json_by_type(T, Tc, NewData).                                                                  

set_json_flatten(Data, Column, Row) ->                                                           
  ResTuple = {list_to_binary(Column), Row},                                                      
  lists:flatten(Data, [ResTuple]).                                                               

set_json_from_sql([], [], Data) -> jsone:encode([{<<"data">>, lists:reverse(Data)}]);            

set_json_from_sql(Columns, [H], Data) ->                                                         
  NewData = set_json_merge(H, Columns, Data),                                                    
  set_json_from_sql([], [], NewData);                                                            

set_json_from_sql(Columns, [H|T], Data) ->                                                       
  NewData = set_json_merge(H, Columns, Data),                                                    
  set_json_from_sql(Columns, T, NewData).                                                        

set_json_merge(Row, Columns, Data) ->                                                            
  TupleRow = json_by_type(tuple_to_list(Row), Columns, []),                                      
  lists:append([TupleRow], Data).                                                                

So set_json_from_sql/3 gives you your Json output after matching set_json_from_sql([], [], Data).

The key points here are that you need to call list_to_binary/1 for strings & atoms. Use jsone to encode Erlang objects to Json: https://github.com/sile/jsone

And, notice format_by_type/1 is used to match against Erlang object types, yes not ideal but works as long as you are aware of your DB's types or you can increase the extra guards to accommodate this.

Upvotes: 2

Roman Rabinovich
Roman Rabinovich

Reputation: 918

This works for me

test()->
    Ma = "Hello World", Mb = "Hello Erlang",
    A = "{\"Messages\" : {{\"Ma\":\"" ++ Ma ++ "\"}, {\"Mb\":\""++Mb++"\"}}, {\"Usernames\" : {\"Username1\":\"usrname1\"}, {\"Username2\":\"usrname2\"}}", 
    io:format("~s~n",[A]).

Output

10> io:format("~s~n",[A]).
{"Messages" : {{"Ma":Hello World"}, {"Mb":Hello Erlang"}}, {"Usernames" : {"Username1":"usrname1"}, {"Username2":"usrname2"}}
ok

or use one of many libraries on github to convert erlang terms to json. My Tuple to JSON module is simple but effective.

Upvotes: 0

NoobsEnslaver
NoobsEnslaver

Reputation: 51

Do it like a pro

-define(JSON_WRAPPER(Proplist), {Proplist}).

-spec from_list(json_proplist()) -> object().
from_list([]) -> new();
from_list(L) when is_list(L) -> ?JSON_WRAPPER(L).

-spec to_binary(atom() | string() | binary() | integer() | float() | pid() | iolist()) -> binary().
to_binary(X) when is_float(X) -> to_binary(mochinum:digits(X));
to_binary(X) when is_integer(X) -> list_to_binary(integer_to_list(X));
to_binary(X) when is_atom(X) -> list_to_binary(atom_to_list(X));
to_binary(X) when is_list(X) -> iolist_to_binary(X);
to_binary(X) when is_pid(X) -> to_binary(pid_to_list(X));
to_binary(X) when is_binary(X) -> X.

-spec recursive_from_proplist(any()) -> object().
    recursive_from_proplist([]) -> new();
    recursive_from_proplist(List) when is_list(List) ->
        case lists:all(fun is_integer/1, List) of
            'true' -> List;
            'false' ->
                from_list([{to_binary(K) ,recursive_from_proplist(V)}
                                   || {K,V} <- List
                                  ])
        end;
    recursive_from_proplist(Other) -> Other.

Upvotes: -1

Elwin Arens
Elwin Arens

Reputation: 1602

If Ma or Mb contains double quotes or whatever control characters, the parsing from string to JSON will fail. This parsing may never occur in Erlang, as Erlang does not have string to JSON conversion built-in.

It's a good idea to use binaries (<<"I am a binary string">>), as lists consume a lot more resources.

We're using jiffy, which is implemented as a NIF and hence is reasonably fast and it allows for document construction like so:

jiffy:decode(<<"{\"foo\": \"bar\"}">>).
{[{<<"foo">>,<<"bar">>}]}
Doc = {[{foo, [<<"bing">>, 2.3, true]}]}.
{[{foo,[<<"bing">>,2.3,true]}]}
jiffy:encode(Doc).
<<"{\"foo\":[\"bing\",2.3,true]}">>

Upvotes: 2

Related Questions