LOADJSON JSON decoder and parser. Syntax: OBJECT = LOADJSON(JSON) OBJECT = LOADJSON([], FILENAME) Description: OBJECT = LOADJSON(JSON) deserializes the value of OBJECT from string JSON encoded in Javascript Object Notation format (JSON). OBJECT = LOADJSON([], FILENAME) performs the same conversion but reading from the file named by string FILENAME. The first argument is ignored. Notes: This function is inspired by a previous function by Tomeu Garau with the same name. He is the true glider man. Main changes are: - Added support for file input. - Changed NaN mapping from string 'NaN' to value null. - Changed character array mapping: only row char vectors map to strings and escaped characters are unescaped properly. There is no one-to-one map between MATLAB/Octave values and JSON values. The conversion is done according to this rules: - Boolean literals (true or false) map to corresponding boolean scalars. - Numeric literals map to double scalars. - Null literal (null) maps to double scalar NaN. - String literals are converted to strings (character row vectors) unescaping character sequences according to section 2.5 of RFC in references. - Objects map to scalar structs with keys as field names and values as field values mapped according to this same set of rules. - Arrays map to column vector cell arrays with elements mapped according to this same set of rules. References: Crockford, D.; 2006: The application/json Media Type for JavaScript Object Notation (JSON). <http://www.ietf.org/rfc/rfc4627.txt> Examples: % Decode from string: object = loadjson('[]') object = loadjson('{}') object = cell2mat(loadjson('[25, 14.3, null]')) object = cell2mat(loadjson('[true,false]')) object = loadjson(['["unescape this:' ... ' \b (backspace) \f (form feed) \n (line feed) \t (tab)' ... ' \r (carriage return) \u001a (escaped unicode character)' ... ' \" (quotation mark) \\ (backslash)"]']) object = loadjson(['[{ "field1": "a" ,"field2" : [1, 2]},' ... ' {"field1" : "b", "field2" :[3, 5] },' ... ' {"field1":"c" , "field2":[4, 3.14]}]']); % Decode from file: object = loadjson([], 'json/example.json') See also: SAVEJSON FOPEN FREAD FCLOSE Authors: Joan Pau Beltran <joanpau.beltran@socib.cat>
0001 function object = loadjson(json, varargin) 0002 %LOADJSON JSON decoder and parser. 0003 % 0004 % Syntax: 0005 % OBJECT = LOADJSON(JSON) 0006 % OBJECT = LOADJSON([], FILENAME) 0007 % 0008 % Description: 0009 % OBJECT = LOADJSON(JSON) deserializes the value of OBJECT from string JSON 0010 % encoded in Javascript Object Notation format (JSON). 0011 % 0012 % OBJECT = LOADJSON([], FILENAME) performs the same conversion but reading 0013 % from the file named by string FILENAME. The first argument is ignored. 0014 % 0015 % Notes: 0016 % This function is inspired by a previous function by Tomeu Garau with the 0017 % same name. He is the true glider man. Main changes are: 0018 % - Added support for file input. 0019 % - Changed NaN mapping from string 'NaN' to value null. 0020 % - Changed character array mapping: only row char vectors map to strings 0021 % and escaped characters are unescaped properly. 0022 % 0023 % There is no one-to-one map between MATLAB/Octave values and JSON values. 0024 % The conversion is done according to this rules: 0025 % - Boolean literals (true or false) map to corresponding boolean scalars. 0026 % - Numeric literals map to double scalars. 0027 % - Null literal (null) maps to double scalar NaN. 0028 % - String literals are converted to strings (character row vectors) 0029 % unescaping character sequences according to section 2.5 of RFC in 0030 % references. 0031 % - Objects map to scalar structs with keys as field names and values as 0032 % field values mapped according to this same set of rules. 0033 % - Arrays map to column vector cell arrays with elements mapped according 0034 % to this same set of rules. 0035 % 0036 % References: 0037 % Crockford, D.; 2006: 0038 % The application/json Media Type for JavaScript Object Notation (JSON). 0039 % <http://www.ietf.org/rfc/rfc4627.txt> 0040 % 0041 % Examples: 0042 % % Decode from string: 0043 % object = loadjson('[]') 0044 % object = loadjson('{}') 0045 % object = cell2mat(loadjson('[25, 14.3, null]')) 0046 % object = cell2mat(loadjson('[true,false]')) 0047 % object = loadjson(['["unescape this:' ... 0048 % ' \b (backspace) \f (form feed) \n (line feed) \t (tab)' ... 0049 % ' \r (carriage return) \u001a (escaped unicode character)' ... 0050 % ' \" (quotation mark) \\ (backslash)"]']) 0051 % object = loadjson(['[{ "field1": "a" ,"field2" : [1, 2]},' ... 0052 % ' {"field1" : "b", "field2" :[3, 5] },' ... 0053 % ' {"field1":"c" , "field2":[4, 3.14]}]']); 0054 % % Decode from file: 0055 % object = loadjson([], 'json/example.json') 0056 % 0057 % See also: 0058 % SAVEJSON 0059 % FOPEN 0060 % FREAD 0061 % FCLOSE 0062 % 0063 % Authors: 0064 % Joan Pau Beltran <joanpau.beltran@socib.cat> 0065 0066 % Copyright (C) 2013-2016 0067 % ICTS SOCIB - Servei d'observacio i prediccio costaner de les Illes Balears 0068 % <http://www.socib.es> 0069 % 0070 % This program is free software: you can redistribute it and/or modify 0071 % it under the terms of the GNU General Public License as published by 0072 % the Free Software Foundation, either version 3 of the License, or 0073 % (at your option) any later version. 0074 % 0075 % This program is distributed in the hope that it will be useful, 0076 % but WITHOUT ANY WARRANTY; without even the implied warranty of 0077 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0078 % GNU General Public License for more details. 0079 % 0080 % You should have received a copy of the GNU General Public License 0081 % along with this program. If not, see <http://www.gnu.org/licenses/>. 0082 0083 error(nargchk(1, 2, nargin, 'struct')); 0084 0085 if nargin > 1 0086 filename = varargin{1}; 0087 [fid, message] = fopen(filename, 'r'); 0088 if fid < 0 0089 error('glider_toolbox:loadjson:FileError', ... 0090 'Could not open file for reading %s: %s.', filename, message); 0091 end 0092 json = fread(fid, '*char')'; 0093 status = fclose(fid); 0094 if status < 0 0095 error('glider_toolbox:loadjson:FileError', ... 0096 'Could not close file %s: %d characters read.', filename, count); 0097 end 0098 end 0099 0100 nspc = ~isspace(json); 0101 step([true nspc]) = diff([0 find(nspc) length(nspc)+1]); 0102 next = cumsum(step); 0103 curs = next(1); 0104 tokn = json(curs); 0105 if tokn == '[' 0106 object = loadjsonArray(json, curs, next); 0107 else 0108 object = loadjsonObject(json, curs, next); 0109 end 0110 0111 end 0112 0113 function [object, curs] = loadjsonObject(json, curs, next) 0114 %LOADJSONOBJECT Decode a scalar struct from a JSON object. 0115 % Initialize empty object: 0116 object = struct(); 0117 % Assert begin-object found, read members if any: 0118 not_bad_object = (json(curs) == '{'); 0119 curs = next(curs + 1); 0120 not_end_object = (json(curs) ~= '}'); 0121 while not_bad_object && not_end_object 0122 % Read key: 0123 [key, curs] = loadjsonString(json, curs); 0124 % Read key-value separator: 0125 curs = next(curs); 0126 if json(curs) ~= ':' 0127 not_bad_object = false; 0128 break 0129 end 0130 curs = next(curs + 1); 0131 % Read value: 0132 [val, curs] = loadjsonValue(json, curs, next); 0133 % Set key-value in object: 0134 object.(key) = val; 0135 % Read member (pair) separator or end-object: 0136 curs = next(curs); 0137 switch json(curs); 0138 case ',' 0139 curs = next(curs + 1); 0140 case '}' 0141 not_end_object = false; 0142 otherwise 0143 not_bad_object = false; 0144 end 0145 end 0146 % Assert no errors and end-object found. 0147 if not_bad_object 0148 curs = next(curs + 1); 0149 else 0150 error('glider_toolbox:loadjson:InvalidObject', ... 0151 'Invalid object at position %d: %s.', ... 0152 curs, json(max(curs-16, 1):curs)); 0153 end 0154 end 0155 0156 function [object, curs] = loadjsonArray(json, curs, next) 0157 %LOADJSONARRAY Decode a cell array from a JSON array. 0158 % Initialize empty cell array: 0159 object = {}; 0160 % Assert begin-array found, read elements if any: 0161 not_bad_array = (json(curs) == '['); 0162 curs = next(curs + 1); 0163 not_end_array = (json(curs) ~= ']'); 0164 while not_bad_array && not_end_array 0165 % Read value: 0166 [val, curs] = loadjsonValue(json, curs, next); 0167 % Set value in object: 0168 object{end+1} = val; 0169 % Read element separator or end-array: 0170 curs = next(curs); 0171 switch json(curs); 0172 case ',' 0173 curs = next(curs + 1); 0174 case ']' 0175 not_end_array = false; 0176 otherwise 0177 not_bad_array = false; 0178 end 0179 end 0180 % Assert no errors and end-array found. 0181 if not_bad_array 0182 curs = next(curs + 1); 0183 else 0184 error('glider_toolbox:loadjson:InvalidArray', ... 0185 'Invalid array at position %d: %s.', ... 0186 curs, json(max(curs-16, 1):curs)); 0187 end 0188 end 0189 0190 function [object, curs] = loadjsonValue(json, curs, next) 0191 %LOADJSONVALUE Decode arbitrary value from a JSON value. 0192 tokn = json(curs); 0193 if tokn == '{' 0194 [object, curs] = loadjsonObject(json, curs, next); 0195 elseif tokn == '[' 0196 [object, curs] = loadjsonArray(json, curs, next); 0197 elseif tokn == '"' 0198 [object, curs] = loadjsonString(json, curs); 0199 elseif tokn == 't' || tokn == 'f' 0200 [object, curs] = loadjsonBoolean(json, curs); 0201 else 0202 [object, curs] = loadjsonNumber(json, curs); 0203 end 0204 end 0205 0206 function [object, curs] = loadjsonBoolean(json, curs) 0207 %LOADJSONBOOLEAN Decode a logical scalar from a JSON boolean value. 0208 if strncmp(json(curs:end), 'true', 4) 0209 object = true; 0210 curs = curs + 4; 0211 elseif strncmp(json(curs:end), 'false', 5); 0212 object = false; 0213 curs = curs + 5; 0214 else 0215 error('glider_toolbox:loadjson:InvalidBoolean', ... 0216 'Invalid boolean at position %d: %s.', ... 0217 curs, json(max(curs-16, 1):curs)); 0218 end 0219 end 0220 0221 function [object, curs] = loadjsonNumber(json, curs) 0222 %LOADJSONNUMBER Decode a numeric scalar value from a JSON number value. 0223 if strncmp(json(curs:end), 'null', 4) 0224 object = nan; 0225 curs = curs + 4; 0226 else 0227 [object, count, emsg, nextidx] = sscanf(json(curs:end), '%f', 1); 0228 curs = curs + nextidx - 1; 0229 if count ~= 1 0230 error('glider_toolbox:loadjson:InvalidNumber', ... 0231 'Invalid number at position %d: %s (%s).', ... 0232 curs, json(max(curs-16, 1):curs), emsg); 0233 end 0234 end 0235 end 0236 0237 function [object, curs] = loadjsonString(json, curs) 0238 %LOADJSONSTRING Decode a row character vector from a JSON string value. 0239 % pattern = '^"([^"\\]+|(?:\\u[0-9a-fA-F]{4}){1,2}|\\["/\\bfnrt])*"'; 0240 string_pattern = '^"(?:\\"|[^"])*"'; 0241 endidx = regexp(json(curs:end), string_pattern, 'once', 'end'); 0242 if isempty(endidx) > 1 0243 error('glider_toolbox:loadjson:InvalidString', ... 0244 'Invalid string at position %d: %s.', ... 0245 curs, json(max(curs-16, 1):curs)); 0246 end 0247 escape_pattern = '(?:\\u[0-9a-fA-F]{4}){1,2}|\\.'; 0248 [escape, split] = ... 0249 regexp(json(curs+1:curs+endidx-2), escape_pattern, 'match', 'split'); 0250 for e = 1:length(escape) 0251 esc = escape{e}; 0252 switch length(esc) 0253 case 12 0254 esc = native2unicode(uint8(hex2dec(esc([3 4; 5 6; 9 10; 11 12]))), ... 0255 'UTF-16BE'); 0256 case 6 0257 esc = native2unicode(uint8(hex2dec(esc([3 4; 5 6]))), 'UTF-16BE'); 0258 case 2 0259 switch esc(2) 0260 case {'"' '\' '/'} 0261 esc = esc(2); 0262 case {'b' 'f' 't' 'n' 'r'} 0263 esc = sprintf(esc); 0264 otherwise 0265 error('glider_toolbox:loadjson:InvalidString', ... 0266 'Invalid string at position %d: %s.', ... 0267 curs, json(max(curs-16, 1):curs)); 0268 end 0269 end 0270 split{2,e} = esc; 0271 end 0272 object = [split{:}]; 0273 curs = curs + endidx; 0274 end 0275