From b075e5d5d5317185ee04ee7280db43991a987126 Mon Sep 17 00:00:00 2001 From: munja Date: Thu, 30 Dec 2021 00:21:02 +0000 Subject: [PATCH] feat: updating mp_jsonout() to support special missing numeric values. Closes #136 --- README.md | 7 + all.sas | 135 ++++++++++++------ base/mp_jsonout.sas | 35 +++-- meta/mm_createwebservice.sas | 24 +++- meta/mm_webout.sas | 16 ++- server/ms_webout.sas | 16 ++- ...jsonout.test.sas => mp_jsonout.test.1.sas} | 5 + tests/crossplatform/mp_jsonout.test.2.sas | 52 +++++++ viya/mv_createwebservice.sas | 24 +++- viya/mv_webout.sas | 20 +-- 10 files changed, 254 insertions(+), 80 deletions(-) rename tests/crossplatform/{mp_jsonout.test.sas => mp_jsonout.test.1.sas} (87%) create mode 100644 tests/crossplatform/mp_jsonout.test.2.sas diff --git a/README.md b/README.md index 9d73ac2..ca08f63 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,13 @@ When contributing to this library, it is therefore important to ensure that all - All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`). +## Breaking Changes + +We are currently on major release v3. The following changes are planned when the next major (breaking) release becomes necessary: + +* Remove `dbg` parameter from mp_jsonout.sas (implement mdebug instead) +* Remove `END_DTTM` and `START_DTTM` from mx_webout JSON + ## Star Gazing If you find this library useful, please leave a [star](https://github.com/sasjs/core/stargazers) and help us grow our star graph! diff --git a/all.sas b/all.sas index 4069d15..a984aa6 100644 --- a/all.sas +++ b/all.sas @@ -7540,19 +7540,20 @@ filename &tempref clear; [sasjs adapter](https://github.com/sasjs/adapter). For more information see https://sasjs.io - @param action Valid values: + @param [in] action Valid values: @li OPEN - opens the JSON @li OBJ - sends a table with each row as an object @li ARR - sends a table with each row in an array @li CLOSE - closes the JSON - - @param ds the dataset to send. Must be a work table. - @param jref= the fileref to which to send the JSON - @param dslabel= the name to give the table in the exported JSON - @param fmt= Whether to keep or strip formats from the table - @param engine= Which engine to use to send the JSON, valid options are: + @param [in] ds The dataset to send. Must be a work table. + @param [out] jref= (_webout) The fileref to which to send the JSON + @param [out] dslabel= The name to give the table in the exported JSON + @param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table + @param engine= (DATASTEP) Which engine to use to send the JSON. Options: @li PROCJSON (default) @li DATASTEP (more reliable when data has non standard characters) + @param missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`) @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. @@ -7567,8 +7568,9 @@ filename &tempref clear; **/ %macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 + ,missing=NULL )/*/STORE SOURCE*/; -%put output location=&jref; +%put &sysmacroname: output location=&jref; %if &action=OPEN %then %do; options nobomfile; data _null_;file &jref encoding='utf-8' ; @@ -7581,10 +7583,16 @@ filename &tempref clear; put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; %if &engine=PROCJSON %then %do; + %if &missing=STRING %then %do; + %put &sysmacroname: Special Missings are not supported in proc json.; + %put &sysmacroname: Switching to DATASTEP engine; + %goto datastep; + %end; data;run;%let tempds=&syslast; proc sql;drop table &tempds; data &tempds /view=&tempds;set &ds; %if &fmt=N %then format _numeric_ best32.;; + /* PRETTY is necessary to avoid line truncation in large files */ proc json out=&jref pretty %if &action=ARR %then nokeys ; ;export &tempds / nosastags fmtnumeric; @@ -7592,6 +7600,7 @@ filename &tempref clear; proc sql;drop view &tempds; %end; %else %if &engine=DATASTEP %then %do; + %datastep: %local cols i tempds; %let cols=0; %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; @@ -7672,7 +7681,15 @@ filename &tempref clear; run; proc format; /* credit yabwon for special null removal */ - value bart ._ - .z = null + value bart + %if &missing=NULL %then %do; + ._ - .z = null + %end; + %else %do; + ._ = [quote()] + . = null + .a - .z = [quote()] + %end; other = [best.]; data;run; %let tempds=&syslast; /* temp table for spesh char management */ @@ -12657,8 +12674,9 @@ data _null_; /* WEBOUT BEGIN */ put ' '; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; + put ' ,missing=NULL '; put ')/*/STORE SOURCE*/; '; - put '%put output location=&jref; '; + put '%put &sysmacroname: output location=&jref; '; put '%if &action=OPEN %then %do; '; put ' options nobomfile; '; put ' data _null_;file &jref encoding=''utf-8'' ; '; @@ -12671,10 +12689,16 @@ data _null_; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' '; put ' %if &engine=PROCJSON %then %do; '; + put ' %if &missing=STRING %then %do; '; + put ' %put &sysmacroname: Special Missings are not supported in proc json.; '; + put ' %put &sysmacroname: Switching to DATASTEP engine; '; + put ' %goto datastep; '; + put ' %end; '; put ' data;run;%let tempds=&syslast; '; put ' proc sql;drop table &tempds; '; put ' data &tempds /view=&tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; + put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; put ' %if &action=ARR %then nokeys ; '; put ' ;export &tempds / nosastags fmtnumeric; '; @@ -12682,6 +12706,7 @@ data _null_; put ' proc sql;drop view &tempds; '; put ' %end; '; put ' %else %if &engine=DATASTEP %then %do; '; + put ' %datastep: '; put ' %local cols i tempds; '; put ' %let cols=0; '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; '; @@ -12762,7 +12787,15 @@ data _null_; put ' run; '; put ' '; put ' proc format; /* credit yabwon for special null removal */ '; - put ' value bart ._ - .z = null '; + put ' value bart '; + put ' %if &missing=NULL %then %do; '; + put ' ._ - .z = null '; + put ' %end; '; + put ' %else %do; '; + put ' ._ = [quote()] '; + put ' . = null '; + put ' .a - .z = [quote()] '; + put ' %end; '; put ' other = [best.]; '; put ' '; put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ '; @@ -12830,7 +12863,7 @@ data _null_; put ' run; '; put '%end; '; put '%mend mp_jsonout; '; - put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); '; + put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL); '; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug '; put ' sasjs_tables; '; put '%local i tempds jsonengine; '; @@ -12895,7 +12928,7 @@ data _null_; put ' '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref '; - put ' ,engine=&jsonengine,dbg=%str(&_debug) '; + put ' ,engine=&jsonengine,missing=&missing '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; @@ -16315,17 +16348,19 @@ run; %mm_webout(CLOSE) - @param action Either FETCH, OPEN, ARR, OBJ or CLOSE - @param ds The dataset to send back to the frontend - @param dslabel= value to use instead of the real name for sending to JSON - @param fmt=(Y) Set to N to send back unformatted values - @param fref=(_webout) The fileref to which to write the JSON + @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE + @param [in] ds The dataset to send back to the frontend + @param [out] dslabel= Value to use instead of table name for sending to JSON + @param [in] fmt=(Y) Set to N to send back unformatted values + @param [out] fref= (_webout) The fileref to which to write the JSON + @param [in] missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`) @version 9.3 @author Allan Bowe **/ -%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); +%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL); %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug sasjs_tables; %local i tempds jsonengine; @@ -16390,7 +16425,7 @@ run; %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref - ,engine=&jsonengine,dbg=%str(&_debug) + ,engine=&jsonengine,missing=&missing ) %end; %else %if &action=CLOSE %then %do; @@ -16614,11 +16649,13 @@ run; %ms_webout(CLOSE) - @param action Either FETCH, OPEN, ARR, OBJ or CLOSE - @param ds The dataset to send back to the frontend - @param dslabel= value to use instead of the real name for sending to JSON - @param fmt=(Y) Set to N to send back unformatted values - @param fref=(_webout) The fileref to which to write the JSON + @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE + @param [in] ds The dataset to send back to the frontend + @param [out] dslabel= value to use instead of table name for sending to JSON + @param [in] fmt= (Y) Set to N to send back unformatted values + @param [out] fref= (_webout) The fileref to which to write the JSON + @param [in] missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`)

SAS Macros

@li mp_jsonout.sas @@ -16633,7 +16670,7 @@ run; **/ -%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y); +%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL); %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug sasjs_tables; @@ -16685,7 +16722,7 @@ run; %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref - ,engine=DATASTEP,dbg=%str(&_debug) + ,engine=DATASTEP,missing=&missing ) %end; %else %if &action=CLOSE %then %do; @@ -17741,8 +17778,9 @@ data _null_; /* WEBOUT BEGIN */ put ' '; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; + put ' ,missing=NULL '; put ')/*/STORE SOURCE*/; '; - put '%put output location=&jref; '; + put '%put &sysmacroname: output location=&jref; '; put '%if &action=OPEN %then %do; '; put ' options nobomfile; '; put ' data _null_;file &jref encoding=''utf-8'' ; '; @@ -17755,10 +17793,16 @@ data _null_; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' '; put ' %if &engine=PROCJSON %then %do; '; + put ' %if &missing=STRING %then %do; '; + put ' %put &sysmacroname: Special Missings are not supported in proc json.; '; + put ' %put &sysmacroname: Switching to DATASTEP engine; '; + put ' %goto datastep; '; + put ' %end; '; put ' data;run;%let tempds=&syslast; '; put ' proc sql;drop table &tempds; '; put ' data &tempds /view=&tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; + put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; put ' %if &action=ARR %then nokeys ; '; put ' ;export &tempds / nosastags fmtnumeric; '; @@ -17766,6 +17810,7 @@ data _null_; put ' proc sql;drop view &tempds; '; put ' %end; '; put ' %else %if &engine=DATASTEP %then %do; '; + put ' %datastep: '; put ' %local cols i tempds; '; put ' %let cols=0; '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; '; @@ -17846,7 +17891,15 @@ data _null_; put ' run; '; put ' '; put ' proc format; /* credit yabwon for special null removal */ '; - put ' value bart ._ - .z = null '; + put ' value bart '; + put ' %if &missing=NULL %then %do; '; + put ' ._ - .z = null '; + put ' %end; '; + put ' %else %do; '; + put ' ._ = [quote()] '; + put ' . = null '; + put ' .a - .z = [quote()] '; + put ' %end; '; put ' other = [best.]; '; put ' '; put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ '; @@ -17914,7 +17967,7 @@ data _null_; put ' run; '; put '%end; '; put '%mend mp_jsonout; '; - put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); '; + put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL); '; put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name '; put ' sasjs_tables SYS_JES_JOB_URI; '; put '%if %index("&_debug",log) %then %let _debug=131; '; @@ -18041,7 +18094,7 @@ data _null_; put '%end; '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) '; + put ' ,jref=&fref,engine=DATASTEP,missing=&missing '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; @@ -21746,13 +21799,15 @@ filename &fref1 clear; %mv_webout(CLOSE) - @param action Either OPEN, ARR, OBJ or CLOSE - @param ds The dataset to send back to the frontend - @param _webout= fileref for returning the json - @param fref=(_mvwtemp) Temp fileref to which to write the output - @param dslabel= value to use instead of the real name for sending to JSON - @param fmt=(Y) change to N to strip formats from output - @param stream=(Y) Change to N if not streaming to _webout + @param [in] action Either OPEN, ARR, OBJ or CLOSE + @param [in] ds The dataset to send back to the frontend + @param [in] _webout= fileref for returning the json + @param [out] fref=(_mvwtemp) Temp fileref to which to write the output + @param [out] dslabel= value to use instead of table name for sending to JSON + @param [in] fmt=(Y) change to N to strip formats from output + @param [in] stream=(Y) Change to N if not streaming to _webout + @param [in] missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`)

SAS Macros

@li mp_jsonout.sas @@ -21762,7 +21817,7 @@ filename &fref1 clear; @author Allan Bowe, source: https://github.com/sasjs/core **/ -%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); +%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL); %global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name sasjs_tables SYS_JES_JOB_URI; %if %index("&_debug",log) %then %let _debug=131; @@ -21889,7 +21944,7 @@ filename &fref1 clear; %end; %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) + ,jref=&fref,engine=DATASTEP,missing=&missing ) %end; %else %if &action=CLOSE %then %do; diff --git a/base/mp_jsonout.sas b/base/mp_jsonout.sas index f6b98fd..829bc5f 100644 --- a/base/mp_jsonout.sas +++ b/base/mp_jsonout.sas @@ -31,19 +31,20 @@ [sasjs adapter](https://github.com/sasjs/adapter). For more information see https://sasjs.io - @param action Valid values: + @param [in] action Valid values: @li OPEN - opens the JSON @li OBJ - sends a table with each row as an object @li ARR - sends a table with each row in an array @li CLOSE - closes the JSON - - @param ds the dataset to send. Must be a work table. - @param jref= the fileref to which to send the JSON - @param dslabel= the name to give the table in the exported JSON - @param fmt= Whether to keep or strip formats from the table - @param engine= Which engine to use to send the JSON, valid options are: + @param [in] ds The dataset to send. Must be a work table. + @param [out] jref= (_webout) The fileref to which to send the JSON + @param [out] dslabel= The name to give the table in the exported JSON + @param [in] fmt= (Y) Whether to keep (Y) or strip (N) formats from the table + @param engine= (DATASTEP) Which engine to use to send the JSON. Options: @li PROCJSON (default) @li DATASTEP (more reliable when data has non standard characters) + @param missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`) @param dbg= DEPRECATED - was used to conditionally add PRETTY to proc json but this can cause line truncation in large files. @@ -58,8 +59,9 @@ **/ %macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 + ,missing=NULL )/*/STORE SOURCE*/; -%put output location=&jref; +%put &sysmacroname: output location=&jref; %if &action=OPEN %then %do; options nobomfile; data _null_;file &jref encoding='utf-8' ; @@ -72,10 +74,16 @@ put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; %if &engine=PROCJSON %then %do; + %if &missing=STRING %then %do; + %put &sysmacroname: Special Missings are not supported in proc json.; + %put &sysmacroname: Switching to DATASTEP engine; + %goto datastep; + %end; data;run;%let tempds=&syslast; proc sql;drop table &tempds; data &tempds /view=&tempds;set &ds; %if &fmt=N %then format _numeric_ best32.;; + /* PRETTY is necessary to avoid line truncation in large files */ proc json out=&jref pretty %if &action=ARR %then nokeys ; ;export &tempds / nosastags fmtnumeric; @@ -83,6 +91,7 @@ proc sql;drop view &tempds; %end; %else %if &engine=DATASTEP %then %do; + %datastep: %local cols i tempds; %let cols=0; %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; @@ -163,7 +172,15 @@ run; proc format; /* credit yabwon for special null removal */ - value bart ._ - .z = null + value bart + %if &missing=NULL %then %do; + ._ - .z = null + %end; + %else %do; + ._ = [quote()] + . = null + .a - .z = [quote()] + %end; other = [best.]; data;run; %let tempds=&syslast; /* temp table for spesh char management */ diff --git a/meta/mm_createwebservice.sas b/meta/mm_createwebservice.sas index 99c2ad1..527f98a 100644 --- a/meta/mm_createwebservice.sas +++ b/meta/mm_createwebservice.sas @@ -90,8 +90,9 @@ data _null_; /* WEBOUT BEGIN */ put ' '; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; + put ' ,missing=NULL '; put ')/*/STORE SOURCE*/; '; - put '%put output location=&jref; '; + put '%put &sysmacroname: output location=&jref; '; put '%if &action=OPEN %then %do; '; put ' options nobomfile; '; put ' data _null_;file &jref encoding=''utf-8'' ; '; @@ -104,10 +105,16 @@ data _null_; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' '; put ' %if &engine=PROCJSON %then %do; '; + put ' %if &missing=STRING %then %do; '; + put ' %put &sysmacroname: Special Missings are not supported in proc json.; '; + put ' %put &sysmacroname: Switching to DATASTEP engine; '; + put ' %goto datastep; '; + put ' %end; '; put ' data;run;%let tempds=&syslast; '; put ' proc sql;drop table &tempds; '; put ' data &tempds /view=&tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; + put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; put ' %if &action=ARR %then nokeys ; '; put ' ;export &tempds / nosastags fmtnumeric; '; @@ -115,6 +122,7 @@ data _null_; put ' proc sql;drop view &tempds; '; put ' %end; '; put ' %else %if &engine=DATASTEP %then %do; '; + put ' %datastep: '; put ' %local cols i tempds; '; put ' %let cols=0; '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; '; @@ -195,7 +203,15 @@ data _null_; put ' run; '; put ' '; put ' proc format; /* credit yabwon for special null removal */ '; - put ' value bart ._ - .z = null '; + put ' value bart '; + put ' %if &missing=NULL %then %do; '; + put ' ._ - .z = null '; + put ' %end; '; + put ' %else %do; '; + put ' ._ = [quote()] '; + put ' . = null '; + put ' .a - .z = [quote()] '; + put ' %end; '; put ' other = [best.]; '; put ' '; put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ '; @@ -263,7 +279,7 @@ data _null_; put ' run; '; put '%end; '; put '%mend mp_jsonout; '; - put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); '; + put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL); '; put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug '; put ' sasjs_tables; '; put '%local i tempds jsonengine; '; @@ -328,7 +344,7 @@ data _null_; put ' '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref '; - put ' ,engine=&jsonengine,dbg=%str(&_debug) '; + put ' ,engine=&jsonengine,missing=&missing '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; diff --git a/meta/mm_webout.sas b/meta/mm_webout.sas index c706315..56c0e2c 100644 --- a/meta/mm_webout.sas +++ b/meta/mm_webout.sas @@ -23,17 +23,19 @@ %mm_webout(CLOSE) - @param action Either FETCH, OPEN, ARR, OBJ or CLOSE - @param ds The dataset to send back to the frontend - @param dslabel= value to use instead of the real name for sending to JSON - @param fmt=(Y) Set to N to send back unformatted values - @param fref=(_webout) The fileref to which to write the JSON + @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE + @param [in] ds The dataset to send back to the frontend + @param [out] dslabel= Value to use instead of table name for sending to JSON + @param [in] fmt=(Y) Set to N to send back unformatted values + @param [out] fref= (_webout) The fileref to which to write the JSON + @param [in] missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`) @version 9.3 @author Allan Bowe **/ -%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y); +%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL); %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug sasjs_tables; %local i tempds jsonengine; @@ -98,7 +100,7 @@ %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref - ,engine=&jsonengine,dbg=%str(&_debug) + ,engine=&jsonengine,missing=&missing ) %end; %else %if &action=CLOSE %then %do; diff --git a/server/ms_webout.sas b/server/ms_webout.sas index 4ad1677..c60f621 100644 --- a/server/ms_webout.sas +++ b/server/ms_webout.sas @@ -20,11 +20,13 @@ %ms_webout(CLOSE) - @param action Either FETCH, OPEN, ARR, OBJ or CLOSE - @param ds The dataset to send back to the frontend - @param dslabel= value to use instead of the real name for sending to JSON - @param fmt=(Y) Set to N to send back unformatted values - @param fref=(_webout) The fileref to which to write the JSON + @param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE + @param [in] ds The dataset to send back to the frontend + @param [out] dslabel= value to use instead of table name for sending to JSON + @param [in] fmt= (Y) Set to N to send back unformatted values + @param [out] fref= (_webout) The fileref to which to write the JSON + @param [in] missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`)

SAS Macros

@li mp_jsonout.sas @@ -39,7 +41,7 @@ **/ -%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y); +%macro ms_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL); %global _webin_file_count _webin_fileref1 _webin_name1 _program _debug sasjs_tables; @@ -91,7 +93,7 @@ %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref - ,engine=DATASTEP,dbg=%str(&_debug) + ,engine=DATASTEP,missing=&missing ) %end; %else %if &action=CLOSE %then %do; diff --git a/tests/crossplatform/mp_jsonout.test.sas b/tests/crossplatform/mp_jsonout.test.1.sas similarity index 87% rename from tests/crossplatform/mp_jsonout.test.sas rename to tests/crossplatform/mp_jsonout.test.1.sas index 156c38e..903e556 100644 --- a/tests/crossplatform/mp_jsonout.test.sas +++ b/tests/crossplatform/mp_jsonout.test.1.sas @@ -34,6 +34,11 @@ data work.test; call symputx('dtval',dtval); run; +%mp_assert( + iftrue=(&syscc=0), + desc=Checking for error condition, + outds=work.test_results +) %mp_assert( iftrue=(&dtval=&compare), diff --git a/tests/crossplatform/mp_jsonout.test.2.sas b/tests/crossplatform/mp_jsonout.test.2.sas new file mode 100644 index 0000000..ac11262 --- /dev/null +++ b/tests/crossplatform/mp_jsonout.test.2.sas @@ -0,0 +1,52 @@ +/** + @file + @brief Testing mp_jsonout.sas macro with special missings + +

SAS Macros

+ @li mp_jsonout.sas + @li mp_assert.sas + +**/ + +filename webref temp; + +data demo; + do x=._,.,.a,.b,.c,.d,.e,-99, 0, 1,2, 3.333333; + output; + end; +run; +%mp_jsonout(OPEN,jref=webref) +%mp_jsonout(OBJ,demo,jref=webref,fmt=N,missing=STRING) +%mp_jsonout(CLOSE,jref=webref) + +data _null_; + infile webref; + input; + putlog _infile_; +run; + +libname web JSON fileref=webref; + +/* proc json turns to char - so switch back to numeric */ +data work.test(keep=x); + set web.demo(rename=(x=y)); + if y ='_' then x=._; + else if anyalpha(y) then x=input(cats(".",y),best.); + else x=input(y,best.); + put (_all_)(=); +run; + +%mp_assert( + iftrue=(&syscc=0), + desc=Checking for error condition with special missing export, + outds=work.test_results +) + +proc compare base=work.demo compare=work.test; +quit; + +%mp_assert( + iftrue=(&sysinfo=0), + desc=Returned json is identical to input table for all special missings, + outds=work.test_results +) \ No newline at end of file diff --git a/viya/mv_createwebservice.sas b/viya/mv_createwebservice.sas index 721f106..d8d18c0 100644 --- a/viya/mv_createwebservice.sas +++ b/viya/mv_createwebservice.sas @@ -238,8 +238,9 @@ data _null_; /* WEBOUT BEGIN */ put ' '; put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0 '; + put ' ,missing=NULL '; put ')/*/STORE SOURCE*/; '; - put '%put output location=&jref; '; + put '%put &sysmacroname: output location=&jref; '; put '%if &action=OPEN %then %do; '; put ' options nobomfile; '; put ' data _null_;file &jref encoding=''utf-8'' ; '; @@ -252,10 +253,16 @@ data _null_; put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; '; put ' '; put ' %if &engine=PROCJSON %then %do; '; + put ' %if &missing=STRING %then %do; '; + put ' %put &sysmacroname: Special Missings are not supported in proc json.; '; + put ' %put &sysmacroname: Switching to DATASTEP engine; '; + put ' %goto datastep; '; + put ' %end; '; put ' data;run;%let tempds=&syslast; '; put ' proc sql;drop table &tempds; '; put ' data &tempds /view=&tempds;set &ds; '; put ' %if &fmt=N %then format _numeric_ best32.;; '; + put ' /* PRETTY is necessary to avoid line truncation in large files */ '; put ' proc json out=&jref pretty '; put ' %if &action=ARR %then nokeys ; '; put ' ;export &tempds / nosastags fmtnumeric; '; @@ -263,6 +270,7 @@ data _null_; put ' proc sql;drop view &tempds; '; put ' %end; '; put ' %else %if &engine=DATASTEP %then %do; '; + put ' %datastep: '; put ' %local cols i tempds; '; put ' %let cols=0; '; put ' %if %sysfunc(exist(&ds)) ne 1 & %sysfunc(exist(&ds,VIEW)) ne 1 %then %do; '; @@ -343,7 +351,15 @@ data _null_; put ' run; '; put ' '; put ' proc format; /* credit yabwon for special null removal */ '; - put ' value bart ._ - .z = null '; + put ' value bart '; + put ' %if &missing=NULL %then %do; '; + put ' ._ - .z = null '; + put ' %end; '; + put ' %else %do; '; + put ' ._ = [quote()] '; + put ' . = null '; + put ' .a - .z = [quote()] '; + put ' %end; '; put ' other = [best.]; '; put ' '; put ' data;run; %let tempds=&syslast; /* temp table for spesh char management */ '; @@ -411,7 +427,7 @@ data _null_; put ' run; '; put '%end; '; put '%mend mp_jsonout; '; - put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); '; + put '%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL); '; put '%global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name '; put ' sasjs_tables SYS_JES_JOB_URI; '; put '%if %index("&_debug",log) %then %let _debug=131; '; @@ -538,7 +554,7 @@ data _null_; put '%end; '; put '%else %if &action=ARR or &action=OBJ %then %do; '; put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt '; - put ' ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) '; + put ' ,jref=&fref,engine=DATASTEP,missing=&missing '; put ' ) '; put '%end; '; put '%else %if &action=CLOSE %then %do; '; diff --git a/viya/mv_webout.sas b/viya/mv_webout.sas index 0baabed..b16f896 100644 --- a/viya/mv_webout.sas +++ b/viya/mv_webout.sas @@ -20,13 +20,15 @@ %mv_webout(CLOSE) - @param action Either OPEN, ARR, OBJ or CLOSE - @param ds The dataset to send back to the frontend - @param _webout= fileref for returning the json - @param fref=(_mvwtemp) Temp fileref to which to write the output - @param dslabel= value to use instead of the real name for sending to JSON - @param fmt=(Y) change to N to strip formats from output - @param stream=(Y) Change to N if not streaming to _webout + @param [in] action Either OPEN, ARR, OBJ or CLOSE + @param [in] ds The dataset to send back to the frontend + @param [in] _webout= fileref for returning the json + @param [out] fref=(_mvwtemp) Temp fileref to which to write the output + @param [out] dslabel= value to use instead of table name for sending to JSON + @param [in] fmt=(Y) change to N to strip formats from output + @param [in] stream=(Y) Change to N if not streaming to _webout + @param [in] missing= (NULL) Special numeric missing values can be sent as NULL + (eg `null`) or as STRING values (eg `".a"` or `".b"`)

SAS Macros

@li mp_jsonout.sas @@ -36,7 +38,7 @@ @author Allan Bowe, source: https://github.com/sasjs/core **/ -%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y); +%macro mv_webout(action,ds,fref=_mvwtemp,dslabel=,fmt=Y,stream=Y,missing=NULL); %global _webin_file_count _webin_fileuri _debug _omittextlog _webin_name sasjs_tables SYS_JES_JOB_URI; %if %index("&_debug",log) %then %let _debug=131; @@ -163,7 +165,7 @@ %end; %else %if &action=ARR or &action=OBJ %then %do; %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt - ,jref=&fref,engine=DATASTEP,dbg=%str(&_debug) + ,jref=&fref,engine=DATASTEP,missing=&missing ) %end; %else %if &action=CLOSE %then %do;