Skip to content

Commit 486a77a

Browse files
committed
[feat] add neuroj - NeuroJSON.io client
1 parent 0356e50 commit 486a77a

File tree

2 files changed

+260
-10
lines changed

2 files changed

+260
-10
lines changed

loadjson.m

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
% to a field in opt. opt can have the following
2727
% fields (first in [.|.] is the default)
2828
%
29+
% Raw [0|1]: if set to 1, loadjson returns the raw JSON string
2930
% SimplifyCell [1|0]: if set to 1, loadjson will call cell2mat
3031
% for each element of the JSON data, and group
3132
% arrays based on the cell2mat rules.
@@ -76,6 +77,7 @@
7677
% returned mmap will be filtered by removing
7778
% entries containing any one of the string patterns
7879
% provided in a cell
80+
% WebOptions {'Username', ..}: additional parameters for urlread
7981
%
8082
% output:
8183
% dat: a cell array, where {...} blocks are converted into cell arrays,
@@ -152,35 +154,49 @@
152154
%
153155

154156
opt = varargin2struct(varargin{:});
157+
webopt = jsonopt('WebOptions', {}, opt);
155158

156159
if (regexp(fname, '^\s*(?:\[.*\])|(?:\{.*\})\s*$', 'once'))
157-
string = fname;
160+
jsonstring = fname;
161+
elseif (regexpi(fname, '^\s*(http|https|ftp|file)://'))
162+
if (jsonopt('Header', 0, opt))
163+
[status, jsonstring] = system(['curl --head "' fname '"']);
164+
jsonstring = regexp(jsonstring, '\[\d+[a-z]*([^\n]+)\[\d+[a-z]*:\s*([^\r\n]*)', 'tokens');
165+
else
166+
jsonstring = urlread(fname, webopt{:});
167+
end
158168
elseif (exist(fname, 'file'))
159169
try
160170
encoding = jsonopt('Encoding', '', opt);
161171
if (isempty(encoding))
162-
string = fileread(fname);
172+
jsonstring = fileread(fname);
163173
else
164174
fid = fopen(fname, 'r', 'n', encoding);
165-
string = fread(fid, '*char')';
175+
jsonstring = fread(fid, '*char')';
166176
fclose(fid);
167177
end
168178
catch
169179
try
170-
string = urlread(fname);
180+
jsonstring = urlread(fname, webopt{:});
171181
catch
172-
string = urlread(['file://', fullfile(pwd, fname)]);
182+
jsonstring = urlread(['file://', fullfile(pwd, fname)]);
173183
end
174184
end
175-
elseif (regexpi(fname, '^\s*(http|https|ftp|file)://'))
176-
string = urlread(fname);
177185
else
178186
error('input file does not exist');
179187
end
180188

189+
if (jsonopt('Raw', 0, opt) || jsonopt('Header', 0, opt))
190+
data = jsonstring;
191+
if (nargout > 1)
192+
mmap = {};
193+
end
194+
return
195+
end
196+
181197
if (jsonopt('BuiltinJSON', 0, opt) && exist('jsondecode', 'builtin'))
182198
try
183-
newstring = regexprep(string, '[\r\n]', '');
199+
newstring = regexprep(jsonstring, '[\r\n]', '');
184200
newdata = jsondecode(newstring);
185201
newdata = jdatadecode(newdata, 'Base64', 1, 'Recursive', 1, varargin{:});
186202
data = newdata;
@@ -191,8 +207,8 @@
191207
end
192208

193209
pos = 1;
194-
inputlen = length(string);
195-
inputstr = string;
210+
inputlen = length(jsonstring);
211+
inputstr = jsonstring;
196212
arraytokenidx = find(inputstr == '[' | inputstr == ']');
197213
arraytoken = inputstr(arraytokenidx);
198214

neuroj.m

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
function [res, restapi, jsonstring] = neuroj(cmd, varargin)
2+
%
3+
% [data, url, rawoutput] = neuroj(command, database, dataset, attachment, ...)
4+
%
5+
% NeuroJSON.io client - browsing/listing/downloading/uploading data
6+
% provided at https://neurojson.io
7+
%
8+
% author: Qianqian Fang (q.fang <at> neu.edu)
9+
%
10+
% input:
11+
% command: a string, must be one of
12+
% 'list':
13+
% - if followed by nothing, list all databases
14+
% - if database is given, list its all datasets
15+
% - if dataset is given, list all its revisions
16+
% 'info': return metadata associated with the specified
17+
% database, dataset or attachment of a dataset
18+
% 'get': must provide database and dataset name, download and
19+
% parse the specified dataset or its attachment
20+
% 'find':
21+
% - if database is a string '/.../', find database by a
22+
% regular expression pattern
23+
% - if database is a struct, find database using
24+
% NeuroJSON's search API
25+
% - if dataset is a string '/.../', find datasets by a
26+
% regular expression pattern
27+
% - if dataset is a struct, find database using
28+
% the _find API
29+
%
30+
% admin commands (require database admin credentials):
31+
% 'put': create database, create dataset under a dataset, or
32+
% upload an attachment under a dataset
33+
% 'delete': delete the specified attachment, dataset or
34+
% database
35+
% jpath: a string in the format of JSONPath, see loadjson help
36+
%
37+
% output:
38+
% data: parsed response data
39+
% url: the URL or REST-API of the desired resource
40+
% jsonstring: the JSON raw data from the URL
41+
%
42+
% example:
43+
% res = neuroj('list') % list all databases under res.database
44+
% res = neuroj('list', 'cotilab') % list all dataset under res.dataset
45+
% res = neuroj('list', 'cotilab', 'CSF_Neurophotonics_2025') % list all versions
46+
% res = neuroj('info') % list metadata of all datasets
47+
% res = neuroj('info', 'cotilab') % list metadata of a given database
48+
% res = neuroj('info', 'cotilab', 'CSF_Neurophotonics_2025') % list dataset header
49+
% res = neuroj('info', 'cotilab', 'CSF_Neurophotonics_2025') % list dataset header
50+
% [res, url, rawstr] = neuroj('get', 'cotilab', 'CSF_Neurophotonics_2025')
51+
% res = neuroj('get', 'cotilab', 'CSF_Neurophotonics_2025')
52+
% userinfo = inputdlg({'Username:', 'Password:'});
53+
% options = {'UserName', userinfo{1}, 'Password', userinfo{2});
54+
% res = neuroj('put', 'sandbox1d', 'newdoc', struct('Author', 'QF') 'weboptions', options);
55+
56+
%
57+
% license:
58+
% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details
59+
%
60+
% -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab)
61+
%
62+
63+
if (nargin == 0)
64+
disp('NeuroJSON.io Client (https://neurojson.io)');
65+
fprintf('Format:\n\t[data, restapi] = neuroj(command, database, dataset, attachment, ...)\n\n');
66+
return
67+
end
68+
69+
dbname = '';
70+
if (~isempty(varargin))
71+
dbname = varargin{1};
72+
end
73+
74+
dsname = '';
75+
if (length(varargin) > 1)
76+
dsname = varargin{2};
77+
end
78+
79+
attachment = '';
80+
if (length(varargin) > 2)
81+
attachment = varargin{3};
82+
end
83+
84+
opt = struct;
85+
if (length(varargin) > 3)
86+
opt = varargin2struct(varargin{4:end});
87+
end
88+
89+
serverurl = getenv('NEUROJSON_IO');
90+
91+
if (isempty(serverurl))
92+
serverurl = 'https://neurojson.io:7777/';
93+
end
94+
95+
serverurl = jsonopt('server', serverurl, opt);
96+
options = jsonopt('weboptions', {}, opt);
97+
opt.weboptions = options;
98+
rev = jsonopt('rev', '', opt);
99+
100+
cmd = lower(cmd);
101+
102+
restapi = serverurl;
103+
104+
if (strcmp(cmd, 'list'))
105+
restapi = [serverurl, 'sys/registry'];
106+
if (~isempty(dbname))
107+
restapi = [serverurl, dbname, '/', '_all_docs'];
108+
if (~isempty(dsname))
109+
restapi = [serverurl, dbname, '/', dsname, '?revs_info=true'];
110+
end
111+
end
112+
jsonstring = loadjson(restapi, opt, 'raw', 1);
113+
res = loadjson(jsonstring, opt);
114+
if (~isempty(dsname))
115+
res = res.(encodevarname('_revs_info'));
116+
elseif (~isempty(dbname))
117+
res.dataset = res.rows;
118+
res = rmfield(res, 'rows');
119+
end
120+
elseif (strcmp(cmd, 'info'))
121+
restapi = [serverurl, '_dbs_info'];
122+
if (~isempty(dbname))
123+
restapi = [serverurl, dbname, '/'];
124+
if (~isempty(dsname))
125+
restapi = [serverurl, dbname, '/', dsname];
126+
if (~isempty(attachment))
127+
restapi = [serverurl, dbname, '/', dsname, '/', attachment];
128+
end
129+
end
130+
end
131+
if (~isempty(dsname) || ~isempty(attachment))
132+
res = loadjson(restapi, opt, 'header', 1);
133+
jsonstring = savejson('', res);
134+
else
135+
jsonstring = loadjson(restapi, opt, 'raw', 1);
136+
res = loadjson(jsonstring);
137+
end
138+
elseif (strcmp(cmd, 'get'))
139+
if (isempty(dsname))
140+
error('get requires a dataset, i.e. document, name');
141+
end
142+
if (isempty(attachment))
143+
restapi = [serverurl, dbname, '/', dsname];
144+
if (~isempty(rev))
145+
restapi = [serverurl, dbname, '/', dsname, '?rev=' rev];
146+
end
147+
jsonstring = loadjson(restapi, opt, 'raw', 1);
148+
res = loadjson(jsonstring, opt);
149+
else
150+
restapi = [serverurl, dbname, '/', dsname, '/', attachment];
151+
[res, jsonstring] = jdlink(restapi);
152+
end
153+
elseif (strcmp(cmd, 'put'))
154+
if (isempty(dbname))
155+
error('put requires at least a database name');
156+
end
157+
restapi = [serverurl, dbname];
158+
putoption = weboptions(opt.weboptions{:});
159+
putoption.RequestMethod = 'post';
160+
putoption.MediaType = 'application/json';
161+
if (~isempty(dsname))
162+
if (isempty(attachment))
163+
error('must provide JSON input to upload');
164+
end
165+
if (ischar(attachment) && exist(attachment, 'file'))
166+
[afpath, afname, afext] = fileparts(attachment);
167+
attname = jsonopt('filename', [afname, afext], opt);
168+
restapi = [serverurl, dbname, '/' dsname '/' attname];
169+
res = websave(attname, restapi, weboptions('RequestMethod', 'put'));
170+
else
171+
restapi = [serverurl, dbname, '/_design/qq/_update/timestamp/' dsname];
172+
jsonstring = savejson('', attachment, 'compact', 1);
173+
res = webwrite(restapi, jsonstring, putoption);
174+
end
175+
else
176+
putoption.RequestMethod = 'put';
177+
res = webwrite(restapi, [], putoption);
178+
end
179+
elseif (strcmp(cmd, 'delete'))
180+
if (isempty(dbname))
181+
error('put requires at least a database name');
182+
end
183+
deloption = weboptions(opt.weboptions{:});
184+
deloption.RequestMethod = 'delete';
185+
restapi = [serverurl, dbname];
186+
if (~isempty(dsname))
187+
restapi = [serverurl, dbname, '/', dsname];
188+
if (~isempty(attachment))
189+
restapi = [serverurl, dbname, '/', dsname, '/', attachment];
190+
end
191+
end
192+
res = webwrite(restapi, [], deloption);
193+
elseif (strcmp(cmd, 'find'))
194+
if (isempty(dbname))
195+
error('find requires at least a search regular expression pattern');
196+
end
197+
if (~isempty(dbname))
198+
if (ischar(dsname) && dbname(1) == '/' && dbname(end) == '/')
199+
[dblist, restapi, jsonstring] = neuroj('list');
200+
res = {};
201+
for i = 1:length(dblist.database)
202+
if (~isempty(regexpi(savejson('', dblist.database{i}, 'compact', 1), dbname(2:end - 1), 'once')))
203+
res{end + 1} = dblist.database{i};
204+
end
205+
end
206+
elseif (isstruct(dbname))
207+
param = join(cellfun(@(x) [x '=' dbname.(x)], fieldnames(dbname), 'UniformOutput', false));
208+
restapi = ['https://neurojson.org/io/search.cgi?' param{:}];
209+
jsonstring = loadjson(restapi, opt, 'raw', 1);
210+
res = loadjson(jsonstrong, opt);
211+
elseif (~isempty(dsname) && ischar(dsname) && dsname(1) == '/' && dsname(end) == '/')
212+
[dslist, restapi, jsonstring] = neuroj('list', dbname);
213+
res = {};
214+
for i = 1:length(dslist.dataset)
215+
if (~isempty(regexpi(dslist.dataset(i).id, dsname(2:end - 1), 'once')))
216+
res{end + 1} = dslist.dataset(i).id;
217+
end
218+
end
219+
elseif (~isempty(dsname) && (isstruct(dsname) || (ischar(dsname) && dsname(1) == '{' && dsname(end) == '}')))
220+
findoption = weboptions(opt.weboptions{:});
221+
findoption.RequestMethod = 'post';
222+
findoption.MediaType = 'application/json';
223+
restapi = [serverurl, dbname, '/_find'];
224+
if (isstruct(dsname))
225+
if (~isfield(dsname, 'selector'))
226+
dsname.selector = {};
227+
end
228+
res = webwrite(restapi, savejson('', dsname, 'compact', 1), findoption);
229+
else
230+
res = webwrite(restapi, dsname, findoption);
231+
end
232+
end
233+
end
234+
end

0 commit comments

Comments
 (0)