spm_jsonwrite.m 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. function varargout = spm_jsonwrite(varargin)
  2. % Serialize a JSON (JavaScript Object Notation) structure
  3. % FORMAT spm_jsonwrite(filename,json)
  4. % filename - JSON filename
  5. % json - JSON structure
  6. %
  7. % FORMAT S = spm_jsonwrite(json)
  8. % json - JSON structure
  9. % S - serialized JSON structure (string)
  10. %
  11. % FORMAT [...] = spm_jsonwrite(...,opts)
  12. % opts - structure of optional parameters:
  13. % indent: string to use for indentation [Default: '']
  14. % replacementStyle: string to control how non-alphanumeric
  15. % characters are replaced [Default: 'underscore']
  16. % convertinfandnan: encode NaN, Inf and -Inf as "null"
  17. % [Default: true]
  18. %
  19. % References:
  20. % JSON Standard: http://www.json.org/
  21. %__________________________________________________________________________
  22. % Copyright (C) 2015-2018 Wellcome Trust Centre for Neuroimaging
  23. % Guillaume Flandin
  24. % $Id: spm_jsonwrite.m 7478 2018-11-08 14:51:54Z guillaume $
  25. %-Input parameters
  26. %--------------------------------------------------------------------------
  27. opts = struct(...
  28. 'indent','',...
  29. 'replacementstyle','underscore',...
  30. 'convertinfandnan',true);
  31. opt = struct([]);
  32. if nargin > 1
  33. if ischar(varargin{1})
  34. filename = varargin{1};
  35. json = varargin{2};
  36. else
  37. filename = '';
  38. json = varargin{1};
  39. opt = varargin{2};
  40. end
  41. if nargin > 2
  42. opt = varargin{3};
  43. end
  44. else
  45. filename = '';
  46. json = varargin{1};
  47. end
  48. fn = fieldnames(opt);
  49. for i=1:numel(fn)
  50. if ~isfield(opts,lower(fn{i})), warning('Unknown option "%s".',fn{i}); end
  51. opts.(lower(fn{i})) = opt.(fn{i});
  52. end
  53. optregistry(opts);
  54. %-JSON serialization
  55. %--------------------------------------------------------------------------
  56. fmt('init',sprintf(opts.indent));
  57. S = jsonwrite_var(json,~isempty(opts.indent));
  58. %-Output
  59. %--------------------------------------------------------------------------
  60. if isempty(filename)
  61. varargout = { S };
  62. else
  63. fid = fopen(filename,'wt');
  64. if fid == -1
  65. error('Unable to open file "%s" for writing.',filename);
  66. end
  67. fprintf(fid,'%s',S);
  68. fclose(fid);
  69. end
  70. %==========================================================================
  71. function S = jsonwrite_var(json,tab)
  72. if nargin < 2, tab = ''; end
  73. if isstruct(json) || isa(json,'containers.Map')
  74. S = jsonwrite_struct(json,tab);
  75. elseif iscell(json)
  76. S = jsonwrite_cell(json,tab);
  77. elseif ischar(json)
  78. if size(json,1) <= 1
  79. S = jsonwrite_char(json);
  80. else
  81. S = jsonwrite_cell(cellstr(json),tab);
  82. end
  83. elseif isnumeric(json) || islogical(json)
  84. S = jsonwrite_numeric(json);
  85. elseif isa(json,'string')
  86. if numel(json) == 1
  87. if ismissing(json)
  88. S = 'null';
  89. else
  90. S = jsonwrite_char(char(json));
  91. end
  92. else
  93. json = arrayfun(@(x)x,json,'UniformOutput',false);
  94. json(cellfun(@(x) ismissing(x),json)) = {'null'};
  95. idx = find(size(json)~=1);
  96. if numel(idx) == 1 % vector
  97. S = jsonwrite_cell(json,tab);
  98. else % array
  99. S = jsonwrite_cell(num2cell(json,setdiff(1:ndims(json),idx(1))),tab);
  100. end
  101. end
  102. elseif isa(json,'datetime') || isa(json,'categorical')
  103. S = jsonwrite_var(string(json));
  104. elseif isa(json,'table')
  105. S = struct;
  106. s = size(json);
  107. vn = json.Properties.VariableNames;
  108. for i=1:s(1)
  109. for j=1:s(2)
  110. if iscell(json{i,j})
  111. S(i).(vn{j}) = json{i,j}{1};
  112. else
  113. S(i).(vn{j}) = json{i,j};
  114. end
  115. end
  116. end
  117. S = jsonwrite_struct(S,tab);
  118. else
  119. if numel(json) ~= 1
  120. json = arrayfun(@(x)x,json,'UniformOutput',false);
  121. S = jsonwrite_cell(json,tab);
  122. else
  123. p = properties(json);
  124. if isempty(p), p = fieldnames(json); end % for pre-classdef
  125. s = struct;
  126. for i=1:numel(p)
  127. s.(p{i}) = json.(p{i});
  128. end
  129. S = jsonwrite_struct(s,tab);
  130. %error('Class "%s" is not supported.',class(json));
  131. end
  132. end
  133. %==========================================================================
  134. function S = jsonwrite_struct(json,tab)
  135. if numel(json) == 1
  136. if isstruct(json), fn = fieldnames(json); else fn = keys(json); end
  137. S = ['{' fmt('\n',tab)];
  138. for i=1:numel(fn)
  139. key = fn{i};
  140. if strcmp(optregistry('replacementStyle'),'hex')
  141. key = regexprep(key,...
  142. '^x0x([0-9a-fA-F]{2})', '${native2unicode(hex2dec($1))}');
  143. key = regexprep(key,...
  144. '0x([0-9a-fA-F]{2})', '${native2unicode(hex2dec($1))}');
  145. end
  146. if isstruct(json), val = json.(fn{i}); else val = json(fn{i}); end
  147. S = [S fmt(tab) jsonwrite_char(key) ':' fmt(' ',tab) ...
  148. jsonwrite_var(val,tab+1)];
  149. if i ~= numel(fn), S = [S ',']; end
  150. S = [S fmt('\n',tab)];
  151. end
  152. S = [S fmt(tab-1) '}'];
  153. else
  154. S = jsonwrite_cell(arrayfun(@(x) {x},json),tab);
  155. end
  156. %==========================================================================
  157. function S = jsonwrite_cell(json,tab)
  158. if numel(json) == 0 ...
  159. || (numel(json) == 1 && iscellstr(json)) ...
  160. || all(all(cellfun(@isnumeric,json))) ...
  161. || all(all(cellfun(@islogical,json)))
  162. tab = '';
  163. end
  164. S = ['[' fmt('\n',tab)];
  165. for i=1:numel(json)
  166. S = [S fmt(tab) jsonwrite_var(json{i},tab+1)];
  167. if i ~= numel(json), S = [S ',']; end
  168. S = [S fmt('\n',tab)];
  169. end
  170. S = [S fmt(tab-1) ']'];
  171. %==========================================================================
  172. function S = jsonwrite_char(json)
  173. % any-Unicode-character-except-"-or-\-or-control-character
  174. % \" \\ \/ \b \f \n \r \t \u four-hex-digits
  175. json = strrep(json,'\','\\');
  176. json = strrep(json,'"','\"');
  177. %json = strrep(json,'/','\/');
  178. json = strrep(json,sprintf('\b'),'\b');
  179. json = strrep(json,sprintf('\f'),'\f');
  180. json = strrep(json,sprintf('\n'),'\n');
  181. json = strrep(json,sprintf('\r'),'\r');
  182. json = strrep(json,sprintf('\t'),'\t');
  183. S = ['"' json '"'];
  184. %==========================================================================
  185. function S = jsonwrite_numeric(json)
  186. if any(imag(json(:)))
  187. error('Complex numbers not supported.');
  188. end
  189. if numel(json) == 0
  190. S = jsonwrite_cell({});
  191. return;
  192. elseif numel(json) > 1
  193. idx = find(size(json)~=1);
  194. if numel(idx) == 1 % vector
  195. if any(islogical(json)) || any(~isfinite(json))
  196. S = jsonwrite_cell(num2cell(json),'');
  197. else
  198. S = ['[' sprintf('%23.16g,',json) ']']; % eq to num2str(json,16)
  199. S(end-1) = ''; % remove last ","
  200. S(S==' ') = [];
  201. end
  202. else % array
  203. S = jsonwrite_cell(num2cell(json,setdiff(1:ndims(json),idx(1))),'');
  204. end
  205. return;
  206. end
  207. if islogical(json)
  208. if json, S = 'true'; else S = 'false'; end
  209. elseif ~isfinite(json)
  210. if optregistry('convertinfandnan')
  211. S = 'null';
  212. else
  213. if isnan(json)
  214. S = 'NaN';
  215. elseif json > 0
  216. S = 'Infinity';
  217. else
  218. S = '-Infinity';
  219. end
  220. end
  221. else
  222. S = num2str(json,16);
  223. end
  224. %==========================================================================
  225. function b = fmt(varargin)
  226. persistent tab;
  227. if nargin == 2 && isequal(varargin{1},'init')
  228. tab = varargin{2};
  229. end
  230. b = '';
  231. if nargin == 1
  232. if varargin{1} > 0, b = repmat(tab,1,varargin{1}); end
  233. elseif nargin == 2
  234. if ~isempty(tab) && ~isempty(varargin{2}), b = sprintf(varargin{1}); end
  235. end
  236. %==========================================================================
  237. function val = optregistry(opts)
  238. persistent options
  239. if isstruct(opts)
  240. options = opts;
  241. else
  242. val = options.(lower(opts));
  243. end