loadjson

PURPOSE ^

LOADJSON JSON decoder and parser.

SYNOPSIS ^

function object = loadjson(json, varargin)

DESCRIPTION ^

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>

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SUBFUNCTIONS ^

DOWNLOAD ^

loadjson.m

SOURCE CODE ^

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

Generated on Fri 06-Oct-2017 10:47:42 by m2html © 2005