Packages
gpb
4.9.2
5.0.0
4.21.7
4.21.6
4.21.5
4.21.4
4.21.3
4.21.2
4.21.1
4.21.0
4.20.0
4.19.9
4.19.8
4.19.7
4.19.6
4.19.5
4.19.4
4.19.3
4.19.2
4.19.1
4.19.0
4.18.0
4.17.7
4.17.6
4.17.5
4.17.3
4.17.2
4.17.1
4.17.0
4.16.2
4.16.1
4.16.0
4.15.2
4.15.1
4.14.2
4.14.1
4.14.0
4.13.0
4.12.0
4.11.2
4.11.1
4.11.0
4.10.6
4.10.5
4.10.4
4.10.3
4.10.2
4.10.1
4.10.0
4.9.3
4.9.2
4.9.1
4.9.0
4.8.0
4.7.3
4.7.2
4.7.1
4.7.0
4.6.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.9
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.2
4.0.1
4.0.0
3.28.1
3.28.0
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.8
3.26.7
3.26.6
3.26.5
3.26.4
3.26.3
3.26.2
3.26.1
3.26.0
3.25.2
3.25.1
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.2
3.23.1
3.23.0
3.22.5
3.22.4
3.22.3
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.3
3.20.2
3.20.0
3.19.0
3.18.10
3.18.9
3.18.8
3.18.7
3.18.6
3.18.5
3.18.4
3.18.3
3.18.2
3.18.1
3.18.0
3.17.13
3.17.12
3.17.11
3.17.10
3.17.9
3.17.8
3.17.5
3.17.4
3.17.3
3.17.2
3.17.1
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
A compiler for Google protocol buffer definitions files for Erlang.
Current section
Files
Jump to
Current section
Files
src/gpb_gen_json_encoders.erl
%%% Copyright (C) 2019 Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% This library is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU Lesser General Public
%%% License as published by the Free Software Foundation; either
%%% version 2.1 of the License, or (at your option) any later version.
%%%
%%% This library is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with this library; if not, write to the Free Software
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%%% MA 02110-1301 USA
%%% @private
%%% @doc Generation of conversion routines from internal format to JSON
-module(gpb_gen_json_encoders).
-export([format_exports/2]).
-export([format_top_function/3]).
-export([format_encoders/3]).
-include("../include/gpb.hrl").
-include("gpb_codegen.hrl").
-include("gpb_compile.hrl").
-import(gpb_lib, [replace_term/2, replace_tree/2,
splice_trees/2, repeat_clauses/2]).
format_exports(Defs, Opts) ->
DoNif = proplists:get_bool(nif, Opts),
[case gpb_lib:get_records_or_maps_by_opts(Opts) of
records ->
?f("-export([to_json/1, to_json/2, to_json/3]).~n");
maps ->
?f("-export([to_json/2, to_json/3]).~n")
end,
[[[begin
NoWrapperFnName = gpb_lib:mk_fn(to_json_msg_, MsgName),
if DoNif ->
?f("-export([~p/1]).~n", [NoWrapperFnName]);
not DoNif ->
?f("-export([~p/1, ~p/2]).~n",
[NoWrapperFnName, NoWrapperFnName])
end
end
|| {{msg,MsgName}, _Fields} <- Defs],
"\n"]
|| gpb_lib:get_bypass_wrappers_by_opts(Opts)]].
format_top_function(Defs, AnRes, Opts) ->
case [Item || {{msg, _}, _}=Item <- Defs] of
[] ->
format_top_function_no_msgs(Opts);
MsgDefs ->
format_top_function_aux(MsgDefs, AnRes, Opts)
end.
format_encoders(Defs, AnRes, Opts) ->
[format_to_json_msgs(Defs, AnRes, Opts),
format_json_helpers(Defs, AnRes, Opts)].
format_top_function_no_msgs(Opts) ->
Mapping = gpb_lib:get_records_or_maps_by_opts(Opts),
[case Mapping of
records ->
["-spec to_json(_) -> no_return().\n",
gpb_codegen:format_fn(
to_json,
fun(Msg) -> call_self(Msg, []) end),
?f("-spec to_json(_,_) -> no_return().\n"),
gpb_codegen:format_fn(
to_json,
fun(Msg,Opts) when tuple_size(Msg) >= 1, is_list(Opts)->
call_self(Msg, element(1,Msg), Opts);
(Msg,MsgName) when tuple_size(Msg) >= 1, is_atom(MsgName)->
call_self(Msg, MsgName, [])
end)];
maps ->
[?f("-spec to_json(_,_) -> no_return().\n"),
gpb_codegen:format_fn(
to_json,
fun(Msg,MsgName) when is_map(Msg), is_atom(MsgName)->
call_self(Msg, MsgName, [])
end)]
end,
?f("-spec to_json(_,_,_) -> no_return().\n"),
gpb_codegen:format_fn(
to_json,
fun(_Msg, MsgName, _Opts) ->
error({gpb_error, {no_such_message, MsgName}})
end)].
format_top_function_aux(MsgDefs, AnRes, Opts) ->
Verify = proplists:get_value(verify, Opts, optionally),
JVerify = proplists:get_value(json_verify, Opts, Verify),
Mapping = gpb_lib:get_records_or_maps_by_opts(Opts),
DoNif = proplists:get_bool(nif, Opts),
[case Mapping of
records ->
[gpb_codegen:format_fn(
to_json,
fun(Msg) -> call_self(Msg, []) end),
gpb_codegen:format_fn(
to_json,
fun(Msg,Opts) when tuple_size(Msg) >= 1, is_list(Opts)->
call_self(Msg, element(1,Msg), Opts);
(Msg,MsgName) when tuple_size(Msg) >= 1, is_atom(MsgName)->
call_self(Msg, MsgName, [])
end)];
maps ->
[gpb_codegen:format_fn(
to_json,
fun(Msg,MsgName) when is_map(Msg), is_atom(MsgName)->
call_self(Msg, MsgName, [])
end)]
end,
gpb_codegen:format_fn(
to_json,
fun(Msg, MsgName, Opts) ->
'<possibly-verify-msg>',
TrUserData = proplists:get_value(user_data, Opts),
case MsgName of
'<msg-name-match>' ->
'to_json_Msg'('Tr'(Msg, TrUserData), 'TrUserData');
X ->
error({gpb_error, {no_such_message, X}})
end
end,
[splice_trees('<possibly-verify-msg>',
case JVerify of
optionally ->
[?expr(case proplists:get_bool(verify, Opts) of
true -> verify_msg(Msg, MsgName, Opts);
false -> ok
end)];
always ->
[?expr(verify_msg(Msg, MsgName, Opts))];
never ->
[]
end),
repeat_clauses(
'<msg-name-match>',
[begin
ElemPath = [MsgName],
Transl = gpb_gen_translators:find_translation(
ElemPath, encode, AnRes),
[replace_term('<msg-name-match>', MsgName),
replace_term('to_json_Msg', gpb_lib:mk_fn(to_json_msg_,
MsgName)),
replace_term('Tr', Transl),
splice_trees('TrUserData', if DoNif -> [];
true -> [?expr(TrUserData)]
end)]
end
|| {{msg,MsgName}, _Fields} <- MsgDefs])])].
format_to_json_msgs(Defs, AnRes, Opts) ->
[format_to_json_msg(MsgName, MsgDef, Defs, AnRes, Opts)
|| {_Type, MsgName, MsgDef} <- gpb_lib:msgs_or_groups(Defs)].
format_to_json_msg(MsgName, MsgDef, Defs, AnRes, Opts) ->
case gpb_lib:get_field_names(MsgDef) of
[] ->
format_to_json_msg_no_fields(MsgName);
FNames ->
format_to_json_msg_aux(MsgName, MsgDef, FNames, Defs, AnRes, Opts)
end.
format_to_json_msg_no_fields(MsgName) ->
FnName = gpb_lib:mk_fn(to_json_msg_, MsgName),
[gpb_codegen:format_fn(
FnName,
fun(_Msg, _TrUserData) ->
tj_finalize_obj(tj_new_object())
end)].
format_to_json_msg_aux(MsgName, MsgDef, FNames, Defs, AnRes, Opts) ->
FVars = [gpb_lib:var_f_n(I) || I <- lists:seq(1, length(FNames))],
JVars = [gpb_lib:var_j_n(I) || I <- lists:seq(1, length(FNames)-1)] ++
[last],
MsgVar = ?expr(M),
TrUserDataVar = ?expr(TrUserData),
{ToJExprs, _} =
lists:mapfoldl(
fun({NewJVar, Field, FVar}, PrevJVar) when NewJVar /= last ->
Tr = gpb_gen_translators:mk_find_tr_fn(MsgName, Field, AnRes),
EncExpr = field_to_json_expr(MsgName, MsgVar, Field, FVar,
PrevJVar, TrUserDataVar,
Defs, Tr, AnRes, Opts),
E = ?expr('NewJ' = '<to-j-expr>',
[replace_tree('NewJ', NewJVar),
replace_tree('<to-j-expr>', EncExpr)]),
{E, NewJVar};
({last, Field, FVar}, PrevJVar) ->
Tr = gpb_gen_translators:mk_find_tr_fn(MsgName, Field, AnRes),
EncExpr = field_to_json_expr(MsgName, MsgVar, Field, FVar,
PrevJVar, TrUserDataVar,
Defs, Tr, AnRes, Opts),
FinalExpr = ?expr(tj_finalize_obj('<enc-expr>'),
[replace_tree('<enc-expr>', EncExpr)]),
{FinalExpr, dummy}
end,
?expr(J0),
lists:zip3(JVars, MsgDef, FVars)),
FnName = gpb_lib:mk_fn(to_json_msg_, MsgName),
FieldMatching =
case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
records ->
gpb_lib:mapping_match(MsgName, lists:zip(FNames, FVars), Opts);
#maps{unset_optional=present_undefined} ->
gpb_lib:mapping_match(MsgName, lists:zip(FNames, FVars), Opts);
#maps{unset_optional=omitted} ->
FMap = gpb_lib:zip_for_non_opt_fields(MsgDef, FVars),
if length(FMap) == length(FNames) ->
gpb_lib:map_match(FMap, Opts);
length(FMap) < length(FNames) ->
?expr('mapmatch' = 'M',
[replace_tree('mapmatch',
gpb_lib:map_match(FMap, Opts)),
replace_tree('M', MsgVar)])
end
end,
[[[gpb_codegen:format_fn(
gpb_lib:mk_fn(to_json_msg_, MsgName),
fun(Msg) ->
%% The undefined is the default TrUserData
'to_json_msg_MsgName'(Msg, undefined)
end,
[replace_term('to_json_msg_MsgName',
gpb_lib:mk_fn(to_json_msg_, MsgName))]),
"\n"]
|| gpb_lib:get_bypass_wrappers_by_opts(Opts)],
gpb_codegen:format_fn(
FnName,
fun('<msg-matching>', TrUserData) ->
J0 = tj_new_object(),
'<to-j-param-exprs>'
end,
[replace_tree('<msg-matching>', FieldMatching),
splice_trees('<to-j-param-exprs>', ToJExprs)])].
field_to_json_expr(MsgName, MsgVar, #?gpb_field{name=FName}=Field,
FVar, PrevJVar, TrUserDataVar, Defs, Tr, AnRes, Opts)->
#?gpb_field{occurrence=Occurrence, type=Type, name=FName}=Field,
TrFVar = gpb_lib:prefix_var("Tr", FVar),
FEncoderExpr = fun(Var) -> type_to_json_expr(Var, Field, TrUserDataVar) end,
JFName = json_field_name(Field, Opts),
Transforms = [replace_term('fieldname', FName),
replace_tree('jfieldname', JFName),
replace_tree('F', FVar),
replace_tree('TrF', TrFVar),
replace_term('Tr', Tr(encode)),
replace_tree('TrUserData', TrUserDataVar),
replace_tree('Json', PrevJVar)],
IsEnum = case Type of
{enum,_} -> true;
_ -> false
end,
%% Shoult EmitTypeDefaults be a run-time option?
%% or alternatively a run-time option?
EmitTypeDefaults =
proplists:get_bool(json_always_print_primitive_fields, Opts),
case Occurrence of
optional ->
EncodeExpr =
case gpb:is_msg_proto3(MsgName, Defs) of
false ->
?expr(begin
'TrF' = 'Tr'('F', 'TrUserData'),
tj_add_field(jfieldname, '<enc>', 'Json')
end,
[replace_tree('<enc>', FEncoderExpr(TrFVar)) |
Transforms]);
true when EmitTypeDefaults ->
?expr(begin
'TrF' = 'Tr'('F', 'TrUserData'),
tj_add_field(jfieldname, '<enc>', 'Json')
end,
[replace_tree('<enc>', FEncoderExpr(TrFVar)) |
Transforms]);
true when Type == string ->
?expr(begin
'TrF' = 'Tr'('F', 'TrUserData'),
case is_empty_string('TrF') of
true ->
'Json';
false ->
tj_add_field(jfieldname, '<enc>',
'Json')
end
end,
[replace_tree('<enc>', FEncoderExpr(TrFVar)) |
Transforms]);
true when Type == bytes ->
?expr(begin
'TrF' = 'Tr'('F', 'TrUserData'),
case iolist_size('TrF') of
0 ->
'Json';
_ ->
tj_add_field(jfieldname, '<enc>',
'Json')
end
end,
[replace_tree('<enc>', FEncoderExpr(TrFVar)) |
Transforms]);
true when IsEnum ->
TypeDefault = gpb:proto3_type_default(Type, Defs),
?expr(
begin
'TrF' = 'Tr'('F', 'TrUserData'),
if 'TrF' =:= '<TypeDefault>';
'TrF' =:= 0 ->
'Json';
true ->
tj_add_field(jfieldname, '<enc>',
'Json')
end
end,
[replace_term('<TypeDefault>', TypeDefault),
replace_tree('<enc>', FEncoderExpr(TrFVar)) |
Transforms]);
true ->
TypeDefault = gpb:proto3_type_default(Type, Defs),
?expr(
begin
'TrF' = 'Tr'('F', 'TrUserData'),
if 'TrF' =:= '<TypeDefault>' ->
'Json';
true ->
tj_add_field(jfieldname, '<enc>',
'Json')
end
end,
[replace_term('<TypeDefault>', TypeDefault),
replace_tree('<enc>', FEncoderExpr(TrFVar)) |
Transforms])
end,
case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
records ->
?expr(
if 'F' == undefined ->
'Json';
true ->
'<encodeit>'
end,
[replace_tree('<encodeit>', EncodeExpr) | Transforms]);
#maps{unset_optional=present_undefined} ->
?expr(
if 'F' == undefined ->
'Json';
true ->
'<encodeit>'
end,
[replace_tree('<encodeit>', EncodeExpr) | Transforms]);
#maps{unset_optional=omitted} ->
?expr(
case 'M' of
'#{fieldname := <F>}' ->
'<encodeit>';
_ ->
'Json'
end,
[replace_tree('M', MsgVar),
replace_tree('#{fieldname := <F>}',
gpb_lib:map_match([{FName,FVar}], Opts)),
replace_tree('<encodeit>', EncodeExpr)
| Transforms])
end;
repeated ->
ElemPath = [MsgName, FName, []],
ElemTransl = gpb_gen_translators:find_translation(
ElemPath, encode, AnRes),
ToJsonExpr =
case Type of
{map, KT, VT} ->
[_KField, VField] = gpb:map_item_pseudo_fields(KT, VT),
VF = type_to_json_expr(?expr(V), VField, TrUserDataVar),
?expr(tj_mapfield_fold(
fun(Elem) -> 'MapElemTr'(Elem, 'TrUserData')
end,
fun(V) -> '<VF>' end,
'Value'),
[replace_term('MapElemTr', ElemTransl),
replace_tree('<VF>', VF),
replace_tree('Value', FEncoderExpr(TrFVar)),
replace_tree('TrUserData', TrUserDataVar)]);
_ ->
?expr(tj_array(['ElemTr'('<enc>', 'TrUserData')
|| Elem <- 'TrF']),
[replace_term('ElemTr', ElemTransl),
replace_tree('<enc>',
FEncoderExpr(?expr(Elem))) |
Transforms])
end,
RTransforms = Transforms ++
[replace_tree('<tojson-repeated>', ToJsonExpr)],
case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
records ->
?expr(
begin
'TrF' = 'Tr'('F', 'TrUserData'),
if 'TrF' == [] -> 'Json';
true -> tj_add_field(jfieldname,
'<tojson-repeated>',
'Json')
end
end,
RTransforms);
#maps{unset_optional=present_undefined} ->
?expr(
begin
'TrF' = 'Tr'('F', 'TrUserData'),
if 'TrF' == [] -> 'Json';
true -> tj_add_field(jfieldname,
'<tojson-repeated>',
'Json')
end
end,
RTransforms);
#maps{unset_optional=omitted} ->
?expr(
case 'M' of
'#{fieldname := <F>}' ->
'TrF' = 'Tr'('F', 'TrUserData'),
if 'TrF' == [] -> 'Json';
true -> tj_add_field(jfieldname,
'<tojson-repeated>',
'Json')
end;
_ ->
'Json'
end,
[replace_tree('M', MsgVar),
replace_tree('#{fieldname := <F>}',
gpb_lib:map_match([{FName,FVar}], Opts)) |
RTransforms])
end;
required ->
?expr(
begin
'TrF' = 'Tr'('F', 'TrUserData'),
tj_add_field(jfieldname, '<encodeit>', 'Json')
end,
[replace_tree('<encodeit>', FEncoderExpr(TrFVar)) |
Transforms])
end;
field_to_json_expr(MsgName, MsgVar, #gpb_oneof{name=FName, fields=OFields},
FVar, PrevJVar, TrUserDataVar, Defs, Tr, AnRes, Opts) ->
ElemPath = [MsgName, FName],
Transl = gpb_gen_translators:find_translation(ElemPath, encode, AnRes),
case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
records ->
?expr(if 'F' =:= undefined -> 'Bin';
true -> '<expr>'
end,
[replace_tree('F', FVar),
replace_tree('Bin', PrevJVar),
replace_tree('<expr>',
field_encode_oneof(
MsgName, MsgVar, FVar, OFields,
Transl, TrUserDataVar, PrevJVar,
Defs, Tr, AnRes, Opts))]);
#maps{unset_optional=present_undefined} ->
?expr(if 'F' =:= undefined -> 'Bin';
true -> '<expr>'
end,
[replace_tree('F', FVar),
replace_tree('Bin', PrevJVar),
replace_tree('<expr>',
field_encode_oneof(
MsgName, MsgVar, FVar, OFields,
Transl, TrUserDataVar, PrevJVar,
Defs, Tr, AnRes, Opts))]);
#maps{unset_optional=omitted, oneof=tuples} ->
?expr(case 'M' of
'#{fname:=F}' -> '<expr>';
_ -> 'Bin'
end,
[replace_tree('#{fname:=F}',
gpb_lib:map_match([{FName, FVar}], Opts)),
replace_tree('M', MsgVar),
replace_tree('Bin', PrevJVar),
replace_tree('<expr>',
field_encode_oneof(
MsgName, MsgVar, FVar, OFields,
Transl, TrUserDataVar, PrevJVar,
Defs, Tr, AnRes, Opts))]);
#maps{unset_optional=omitted, oneof=flat} ->
?expr(case 'M' of
'#{tag:=Val}' -> '<expr>';
_ -> 'Bin'
end,
[replace_tree('M', MsgVar),
replace_tree('Bin', PrevJVar),
repeat_clauses(
'#{tag:=Val}',
field_encode_oneof_flat(
'#{tag:=Val}', MsgName, MsgVar, FVar, OFields,
Transl, TrUserDataVar, PrevJVar,
Defs, Tr, AnRes, Opts))])
end.
field_encode_oneof(MsgName, MsgVar, FVar, OFields,
Transl, TrUserDataVar, PrevJVar, Defs, Tr, AnRes, Opts) ->
TVar = gpb_lib:prefix_var("T", FVar),
?expr(case 'Tr'('F', 'TrUserData') of
'{tag,TVar}' -> '<expr>'
end,
[replace_term('Tr', Transl),
replace_tree('TrUserData', TrUserDataVar),
replace_tree('F', FVar),
repeat_clauses(
'{tag,TVar}',
[begin
TagTuple = ?expr({tag,'TVar'}, [replace_term(tag,Name),
replace_tree('TVar', TVar)]),
%% undefined is already handled, we have a match,
%% the field occurs, as if it had been required
OField2 = OField#?gpb_field{occurrence=required},
Tr2 = Tr({update_elem_path,Name}),
EncExpr = field_to_json_expr(MsgName, MsgVar, OField2, TVar,
PrevJVar, TrUserDataVar,
Defs, Tr2, AnRes, Opts),
[replace_tree('{tag,TVar}', TagTuple),
replace_tree('<expr>', EncExpr)]
end
|| #?gpb_field{name=Name}=OField <- OFields])]).
field_encode_oneof_flat(ClauseMarker, MsgName, MsgVar, FVar, OFields,
Transl, TrUserDataVar, PrevJVar, Defs, Tr, AnRes, Opts) ->
OFVar = gpb_lib:prefix_var("O", FVar),
[begin
MatchPattern = gpb_lib:map_match([{Name, OFVar}], Opts),
%% undefined is already handled, we have a match,
%% the field occurs, as if it had been required
OField2 = OField#?gpb_field{occurrence=required},
Tr2 = Tr({update_elem_path,Name}),
EncExpr = field_to_json_expr(MsgName, MsgVar, OField2, OFVar,
PrevJVar, TrUserDataVar,
Defs, Tr2, AnRes, Opts),
TrEncExpr = ?expr('Tr'('EncExpr', 'TrUserData'),
[replace_term('Tr', Transl),
replace_tree('EncExpr', EncExpr),
replace_tree('TrUserData', TrUserDataVar)]),
[replace_tree(ClauseMarker, MatchPattern),
replace_tree('<expr>', TrEncExpr)]
end
|| #?gpb_field{name=Name}=OField <- OFields].
type_to_json_expr(Var, #?gpb_field{type=Type}, TrUserDataVar) ->
case Type of
Int32 when Int32 == sint32;
Int32 == int32;
Int32 == uint32 ->
Var;
Int64 when Int64 == sint64;
Int64 == int64;
Int64 == uint64 ->
?expr(tj_string(integer_to_list('Value')),
[replace_tree('Value', Var)]);
bool ->
Var;
{enum,_EnumName} ->
?expr(if is_integer('Value') -> 'Value';
is_atom('Value') -> tj_string(atom_to_list('Value'))
end,
[replace_tree('Value', Var)]);
Int32 when Int32 == fixed32;
Int32 == sfixed32 ->
Var;
Int64 when Int64 == fixed64;
Int64 == sfixed64 ->
?expr(tj_string(integer_to_list('Value')),
[replace_tree('Value', Var)]);
Float when Float == float;
Float == double ->
?expr(if is_integer('Value') -> 'Value';
is_float('Value') -> tj_float('Value');
'Value';
'Value' == infinity -> <<"Infinity">>;
'Value' == '-infinity' -> <<"-Infinity">>;
'Value' == nan -> <<"NaN">>
end,
[replace_tree('Value', Var)]);
string ->
?expr(tj_string('Value'),
[replace_tree('Value', Var)]);
bytes ->
%% Need option for "websafe base64 escape with padding"?
?expr(tj_bytes('Value'),
[replace_tree('Value', Var)]);
{msg,MsgName} ->
FnName = gpb_lib:mk_fn(to_json_msg_, MsgName),
?expr('to_json_msg_...'('Value', 'TrUserData'),
[replace_term('to_json_msg_...', FnName),
replace_tree('Value', Var),
replace_tree('TrUserData', TrUserDataVar)]);
{map,_KT,_VT} ->
Var; % handled on a higher level
{group,GName} ->
FnName = gpb_lib:mk_fn(to_json_msg_, GName),
?expr('to_json_group_...'('Value', 'TrUserData'),
[replace_term('to_json_group_...', FnName),
replace_tree('Value', Var),
replace_tree('TrUserData', TrUserDataVar)])
end.
%%% --- helpers ---
format_json_helpers(_Defs, #anres{map_types=MapTypes}=AnRes, Opts) ->
HaveMapfields = sets:size(MapTypes) > 0,
[case gpb_lib:json_object_format_by_opts(Opts) of
eep18 ->
format_eep18_object_helpers();
{proplist} ->
format_untagged_proplist_object_helpers();
{Atom, proplist} ->
format_tagged_proplist_object_helpers(Atom);
map ->
format_map_object_helpers(Opts)
end,
case gpb_lib:json_array_format_by_opts(Opts) of
list ->
format_plain_list_array_helpers();
{Atom, list} ->
format_tagged_list_array_helpers(Atom)
end,
[[case gpb_lib:get_records_or_maps_by_opts(Opts) of
records ->
format_mapfield_record_helpers();
maps ->
format_mapfield_map_helpers(Opts)
end,
format_mapfield_key_to_str_helpers()] || HaveMapfields],
format_json_type_helpers(AnRes, Opts)].
%% -- object helpers --
format_eep18_object_helpers() ->
["%% eep18 object format helpers\n",
"%% - proplist() % for non-empty objects\n"
"%% - [{}] % for empty objects\n"
"%% For example jsx, eep18-conforming libs\n",
gpb_lib:nowarn_unused_function(tj_new_object, 0),
gpb_codegen:format_fn(
tj_new_object,
fun() -> [{}] end),
gpb_lib:nowarn_unused_function(tj_add_field, 3),
gpb_codegen:format_fn(
tj_add_field,
fun(FieldName, Value, [{}]) -> [{FieldName, Value}];
(FieldName, Value, Object) -> [{FieldName, Value} | Object]
end),
gpb_lib:nowarn_unused_function(tj_finalize_obj, 1),
gpb_codegen:format_fn(
tj_finalize_obj,
fun(Object) -> lists:reverse(Object) end)].
format_untagged_proplist_object_helpers() ->
["%% untagged proplist object format helpers\n",
"%% - {proplist()}\n"
"%% For example for jiffy\n",
gpb_lib:nowarn_unused_function(tj_new_object, 0),
gpb_codegen:format_fn(
tj_new_object,
fun() -> {[]} end),
gpb_lib:nowarn_unused_function(tj_add_field, 3),
gpb_codegen:format_fn(
tj_add_field,
fun(FieldName, Value, {Object}) ->
{[{FieldName, Value} | Object]}
end),
gpb_lib:nowarn_unused_function(tj_finalize_obj, 1),
gpb_codegen:format_fn(
tj_finalize_obj,
fun({Object}) -> {lists:reverse(Object)} end)].
format_tagged_proplist_object_helpers(Tag) ->
["%% tagged proplist object format helpers\n",
"%% - {"++atom_to_list(Tag)++", proplist()}\n"
"%% For example for mochjson2\n",
gpb_lib:nowarn_unused_function(tj_new_object, 0),
gpb_codegen:format_fn(
tj_new_object,
fun() -> {struct, []} end,
[replace_term(struct, Tag)]),
gpb_lib:nowarn_unused_function(tj_add_field, 3),
gpb_codegen:format_fn(
tj_add_field,
fun(FieldName, Value, {struct, Object}) ->
{struct, [{FieldName, Value} | Object]}
end,
[replace_term(struct, Tag)]),
gpb_lib:nowarn_unused_function(tj_finalize_obj, 1),
gpb_codegen:format_fn(
tj_finalize_obj,
fun({struct, Object}) -> {struct, lists:reverse(Object)} end,
[replace_term(struct, Tag)])].
format_map_object_helpers(Opts) ->
["%% map object format helpers\n"
"%% For example jsx, jiffy, others\n",
gpb_lib:nowarn_unused_function(tj_new_object, 0),
gpb_codegen:format_fn(
tj_new_object,
fun() -> '#{}' end,
[replace_tree('#{}', gpb_lib:map_create([], Opts))]),
gpb_lib:nowarn_unused_function(tj_add_field, 3),
case gpb_lib:target_has_variable_key_map_update(Opts) of
true ->
{Object, FieldName, Value} =
{?expr(Object), ?expr(FieldName), ?expr(Value)},
gpb_codegen:format_fn(
tj_add_field,
fun(FieldName, Value, Object) ->
'Object#{FieldName => Value}'
end,
[replace_tree(
'Object#{FieldName => Value}',
gpb_lib:map_set(Object, [{FieldName,Value}], []))]);
false ->
gpb_codegen:format_fn(
tj_add_field,
fun(FieldName, Value, Object) ->
maps:put(FieldName, Value, Object)
end)
end,
gpb_lib:nowarn_unused_function(tj_finalize_obj, 1),
gpb_codegen:format_fn(
tj_finalize_obj,
fun(Object) -> Object end)].
%% -- mapfield helpers, ie for map<_,_> --
format_mapfield_record_helpers() ->
[gpb_lib:nowarn_unused_function(tj_mapfield_fold, 3),
gpb_codegen:format_fn(
tj_mapfield_fold,
fun(TrElemF, ValueToJsonF, MapfieldElems) ->
tj_finalize_obj(
lists:foldl(
fun(Elem, Obj) ->
{_, K, V} = TrElemF(Elem),
tj_add_field(tj_mapfield_key_to_str(K),
ValueToJsonF(V),
Obj)
end,
tj_new_object(),
MapfieldElems))
end)].
format_mapfield_map_helpers(Opts) ->
{K, V} = {?expr(K), ?expr(V)},
[gpb_codegen:format_fn(
tj_mapfield_fold,
fun(TrElemF, ValueToJsonF, MapfieldElems) ->
tj_finalize_obj(
lists:foldl(
fun(Elem, Obj) ->
'#{key := K, value := V}' = TrElemF(Elem),
tj_add_field(tj_mapfield_key_to_str(K),
ValueToJsonF(V),
Obj)
end,
tj_new_object(),
MapfieldElems))
end,
[replace_tree('#{key := K, value := V}',
gpb_lib:map_match([{key,K}, {value,V}], Opts))])].
format_mapfield_key_to_str_helpers() ->
[gpb_lib:nowarn_unused_function(tj_mapfield_key_to_str, 1),
gpb_codegen:format_fn(
tj_mapfield_key_to_str,
fun(N) when is_integer(N) -> tj_string(integer_to_list(N));
(true) -> tj_string("true");
(false) -> tj_string("false");
(String) when is_list(String); is_binary(String) ->
tj_string(String)
end)].
%% -- array helpers --
format_plain_list_array_helpers() ->
%% inline it?
[gpb_lib:nowarn_unused_function(tj_array, 1),
gpb_codegen:format_fn(tj_array,
fun(L) -> L end)].
format_tagged_list_array_helpers(Tag) ->
%% inline it?
[gpb_lib:nowarn_unused_function(tj_array, 1),
gpb_codegen:format_fn(tj_array,
fun(L) -> {array, L} end,
[replace_term(array, Tag)])].
%% -- other helpers --
format_json_type_helpers(#anres{used_types=UsedTypes,
map_types=MapTypes}, Opts) ->
HaveMapfields = sets:size(MapTypes) > 0,
HaveInt64 = lists:any(fun(T) -> gpb_lib:smember(T, UsedTypes) end,
[sint64, int64, uint64, fixed64, sfixed64]),
HaveEnum = lists:any(fun({enum,_}) -> true;
(_) -> false
end,
sets:to_list(UsedTypes)),
NeedFloatType = lists:any(fun(T) -> gpb_lib:smember(T, UsedTypes) end,
[float, double]),
%% map<_,_> keys are strings
%% int64 types and enums also encode to strings
NeedStringType = (gpb_lib:smember(string, UsedTypes)
orelse HaveMapfields
orelse HaveInt64
orelse HaveEnum),
NeedBytesType = gpb_lib:smember(bytes, UsedTypes),
[[gpb_codegen:format_fn(
tj_float,
%% Rely on the json libs to produce a good float
%% (or should we produce a 15 digit %g as a string
%% or by other means a roundtrip-able float-string?)
fun(F) -> F
end) || NeedFloatType],
[case gpb_lib:json_string_format_by_opts(Opts) of
binary ->
[gpb_codegen:format_fn(
tj_string,
fun(Value) -> unicode:characters_to_binary(Value)
end)];
list ->
[gpb_codegen:format_fn(
tj_string,
fun(Value) -> unicode:characters_to_list(
unicode:characters_to_binary(Value))
end)]
end || NeedStringType],
[gpb_codegen:format_fn(
tj_bytes,
fun(Value) -> base64:encode(iolist_to_binary(Value))
end) || NeedBytesType]].
%% -- misc --
json_field_name(#?gpb_field{name=FName}=Field, Opts) ->
JFName = case proplists:get_bool(json_preserve_proto_field_names, Opts) of
true -> atom_to_list(FName);
false -> gpb_lib:get_field_json_name(Field)
end,
case gpb_lib:json_key_format_by_opts(Opts) of
atom ->
erl_syntax:atom(JFName);
binary ->
%% Want <<"fname">> and not <<102,110,97,109,101>>
%% so make a text node
erl_syntax:text(?ff("<<~p>>", [JFName]));
string ->
erl_syntax:string(JFName)
end.