function data = p_json(string)
% DATA = P_JSON(string)
% This function parses a JSON string and returns a cell array with the
% parsed data. JSON objects are converted to structures and JSON arrays are
% converted to cell arrays.
%
% p_json(filename) loads a file named <filename> instead.
%
% =========================================================
% *** HIGHLY PORTABLE VERSION ***
%
% based on work from:
%
% http://json-schema.org/
% &
% http://www.mathworks.com/matlabcentral/fileexchange/
%
% F.Glineur 23393-another-json-parser: Faster, Clearer & More robust than #20565
% J.Feenstra 20565-json-parser
%
% ---------------------------------------------------------
% parts (c) Nedialko Krouchev Nov.2009
% 2010-06-15 Dan Ellis dpwe@ee.columbia.edu - extended for quotes, files
%
% =========================================================
if exist(string, 'file') > 0 % assume we won't be unlucky
disp(['loading json file ',string]);
f = fopen(string, 'r');
string = fread(f,Inf,'char=>char')';
fclose(f);
else
disp(['parsing json string...']);
end
global pos inStr len esc index_esc len_esc
pos = 1; len = length(string); inStr = string;
% String delimiters and escape chars identified to improve speed:
esc = find(inStr=='"' | inStr=='\' | inStr==char(39) | inStr==':'); % comparable to: regexp(inStr, '["\\]');
index_esc = 1; len_esc = length(esc);
% skip any prefix
startchr = min([find(inStr == '{'),find(inStr == '[')]);
if startchr > 1
pos = startchr;
disp(['p_json: warn: skipping initial string "',inStr(1:pos-1),'"']);
end
if pos <= len
switch(next_char)
case '{'
data = parse_object;
case '['
data = parse_array;
otherwise
error_pos('Outer level structure must be an object or an array');
end
end % if
function object = parse_object
parse_char('{');
object = [];
if next_char ~= '}'
while 1
str = parseStr;
%disp(['parse_object: name="',str,'"']);
if isempty(str)
error_pos('Name of value at position %d cannot be empty');
end
parse_char(':');
val = parse_value;
eval( sprintf( 'object.%s = val;', valid_field(str) ) );
if next_char == '}'
break;
end
parse_char(',');
end
end
parse_char('}');
%disp(['parse_object: got ',num2str(length(object)),' fields']);
% end ----------------------------------------------------------------
function object = parse_array
parse_char('[');
object = cell(0, 1);
if next_char ~= ']'
while 1
val = parse_value;
object{end+1} = val;
if next_char == ']'
break;
end
parse_char(',');
end
end
parse_char(']');
% end ----------------------------------------------------------------
function parse_char(c)
global pos inStr len
skip_whitespace;
if pos > len | inStr(pos) ~= c
error_pos(sprintf('Expected %c at position %%d', c));
else
pos = pos + 1;
skip_whitespace;
end
% end ----------------------------------------------------------------
function c = next_char
global pos inStr len
skip_whitespace;
if pos > len
c = [];
else
c = inStr(pos);
end
% end ----------------------------------------------------------------
function skip_whitespace
global pos inStr len
while pos <= len & isspace(inStr(pos))
pos = pos + 1;
end
% end ----------------------------------------------------------------
function str = parseStr
global pos inStr len esc index_esc len_esc
% len, ns = length(inStr), keyboard
delim = '';
% if inStr(pos) ~= '"' && inStr(pos) ~= char(39)
% error_pos('String starting with " expected at position %d');
% else
if inStr(pos) == '"' || inStr(pos) == char(39)
delim = inStr(pos);
pos = pos + 1;
end
str = '';
while pos <= len
while index_esc <= len_esc & esc(index_esc) < pos
index_esc = index_esc + 1;
end
if index_esc > len_esc
str = [str inStr(pos:len)];
pos = len + 1;
break;
else
str = [str inStr(pos:esc(index_esc)-1)];
pos = esc(index_esc);
end
nstr = length(str); switch inStr(pos)
case '"'
pos = pos + 1;
if delim == '"'
return;
else
str(nstr+1) = '"';
end
case char(39)
pos = pos + 1;
if delim == char(39)
return;
else
str(nstr+1) = char(39);
end
case '\'
if pos+1 > len
error_pos('End of file reached right after escape character');
end
pos = pos + 1;
switch inStr(pos)
case {'"' '\' '/' char(39)}
str(nstr+1) = inStr(pos);
pos = pos + 1;
case {'b' 'f' 'n' 'r' 't'}
str(nstr+1) = sprintf(['\' inStr(pos)]);
pos = pos + 1;
case 'u'
if pos+4 > len
error_pos('End of file reached in escaped unicode character');
end
str(nstr+(1:6)) = inStr(pos-1:pos+4);
pos = pos + 5;
end
case ':'
% colon after undelimited string is terminator
if length(delim) == 0 % string without delimiters
return
end
% else, just put it in the string
str = [str,':'];
pos = pos+1;
otherwise % should never happen
str(nstr+1) = inStr(pos), keyboard
pos = pos + 1;
end
end
error_pos('End of file while expecting end of inStr');
% end ----------------------------------------------------------------
function num = parse_number
global pos inStr len
[num, one, err, delta] = sscanf(inStr(pos:min(len,pos+20)), '%f', 1);
if ~isempty(err)
error_pos('Error reading number at position %d');
end
pos = pos + delta-1;
% end ----------------------------------------------------------------
function val = parse_value
global pos inStr len
true = 1; false = 0;
switch(lower(inStr(pos)))
case '"'
val = parseStr;
return;
case char(39) % single quote
val = parseStr;
return;
case '['
val = parse_array;
return;
case '{'
val = parse_object;
return;
case {'-','0','1','2','3','4','5','6','7','8','9'}
val = parse_number;
return;
case 't'
if pos+3 <= len & strcmp(lower(inStr(pos:pos+3)), 'true')
val = true;
pos = pos + 4;
return;
end
case 'f'
if pos+4 <= len & strcmp(lower(inStr(pos:pos+4)), 'false')
val = false;
pos = pos + 5;
return;
end
case 'n'
if pos+3 <= len & strcmp(lower(inStr(pos:pos+3)), 'null')
val = [];
pos = pos + 4;
return;
end
end
error_pos('Value expected at position %d');
% end ----------------------------------------------------------------
function error_pos(msg)
global pos inStr len
poShow = max(min([pos-15 pos-1 pos pos+20],len),1);
if poShow(3) == poShow(2)
poShow(3:4) = poShow(2)+[0 -1]; % display nothing after
end
msg = [sprintf(msg, pos) ': ' ...
inStr(poShow(1):poShow(2)) '<error>' inStr(poShow(3):poShow(4)) ];
error( ['JSONparser:invalidFormat: ' msg] );
% end ----------------------------------------------------------------
function str = valid_field(str)
% From MATLAB doc: field names must begin with a letter, which may be
% followed by any combination of letters, digits, and underscores.
% Invalid characters will be converted to underscores, and the prefix
% "s_" will be added if first character is not a letter.
if ~isletter(str(1))
str = ['s_' str];
end
str(~isletter(str) & ~('0' <= str & str <= '9')) = '_';
% end ----------------------------------------------------------------
% end % of p0json (main)