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;