Skip to content

Commit fea481e

Browse files
committed
[doc] add line-by-line comment on examples, add jsonset/jsonget
1 parent e1d386d commit fea481e

1 file changed

Lines changed: 250 additions & 15 deletions

File tree

README.rst

Lines changed: 250 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ In JSONLab 2.9.8 and later versions, a unified file loading and saving interface
396396
is provided for JSON, binary JSON and HDF5, including ``loadjd`` and ``savejd``
397397
for reading and writing below files types:
398398

399-
- JSON based files: ``.json`, ``.jdt`` (text JData file), ``.jmsh`` (text JMesh file),
399+
- JSON based files: ``.json``, ``.jdt`` (text JData file), ``.jmsh`` (text JMesh file),
400400
``.jnii`` (text JNIfTI file), ``.jnirs`` (text JSNIRF file)
401401
- BJData based files: ``.bjd``, ``.jdb`` (binary JData file), ``.bmsh`` (binary JMesh file),
402402
``.bnii`` (binary JNIfTI file), ``.bnirs`` (binary JSNIRF file), ``.pmat`` (MATLAB session file)
@@ -406,7 +406,7 @@ for reading and writing below files types:
406406

407407

408408
In the below section, we provide a few examples on how to us each of the
409-
core functions for encoding/decoding JSON/UBJSON/MessagePack data.
409+
core functions for encoding/decoding JSON/Binary JSON/MessagePack data.
410410

411411
----------
412412
savejson.m
@@ -420,14 +420,40 @@ savejson.m
420420
2 8 4;2 8 6;3 8 4;3 8 7;5 8 6;5 8 7],...
421421
'MeshCreator','FangQ','MeshTitle','T6 Cube',...
422422
'SpecialData',[nan, inf, -inf]);
423+
424+
% convert any matlab variables to JSON (variable name is used as the root name)
423425
savejson(jsonmesh)
426+
427+
% convert matlab variables to JSON with a root-name "jmesh"
424428
savejson('jmesh',jsonmesh)
429+
430+
% an empty root-name directly embed the data in the root {}
431+
% the compact=1 flag prints JSON without white-space in a single-line
425432
savejson('',jsonmesh,'Compact',1)
433+
434+
% if 3 inputs are given, the 3rd parameter defines the output file name
426435
savejson('jmesh',jsonmesh,'outputfile.json')
427-
savejson('',jsonmesh,'ArrayIndent',0,'FloatFormat','\t%.5g','FileName','outputfile2.json')
436+
437+
% param/value pairs can be provided after the 2nd input to customize outputs
438+
% if you want to use params/values and save JSON to a file, you must use the 'filename' to set output file
439+
savejson('',jsonmesh,'FileName','outputfile2.json','ArrayIndent',0,'FloatFormat','\t%.5g')
440+
441+
% jsonlab utilizes JData annotations to encode complex/sparse ND-arrays
428442
savejson('cpxrand',eye(5)+1i*magic(5))
443+
444+
% when setting 'BuiltinJSON' to 1, savejson calls jsonencode.m in MATLAB (R2016+)
445+
% or Octave (v7+) to convert data to JSON; this is typically faster, but does not
446+
% support all features native savejson offers
447+
savejson('cpxrand',eye(5)+1i*magic(5), 'BuiltinJSON', 1)
448+
449+
% JData annotations also allows one to compress binary strongly-typed data and store in the JSON
450+
% gzip/zlib are natively supported in MATLAB and Octave; using ZMat toolbox, one can use lz4, lzma, blosc2 etc compressors
429451
savejson('ziparray',eye(10),'Compression','zlib','CompressArraySize',1)
452+
453+
% 'ArrayToStruct' flag forces all arrays to use the JData ND array annotations to preserve types
430454
savejson('',jsonmesh,'ArrayToStruct',1)
455+
456+
% JData supports compact storage of special matrices using the '_ArrayShape_' annotation
431457
savejson('',eye(10),'UseArrayShape',1)
432458
433459
----------
@@ -436,54 +462,219 @@ loadjson.m
436462

437463
.. code-block::
438464
465+
% loadjson can directly parse a JSON string if it starts with "[" or "{", here is an empty object
439466
loadjson('{}')
467+
468+
% loadjson can also parse complex JSON objects in a string form
440469
dat=loadjson('{"obj":{"string":"value","array":[1,2,3]}}')
470+
471+
% if the input is a file name, loadjson reads the file and parse the data inside
441472
dat=loadjson(['examples' filesep 'example1.json'])
473+
474+
% param/value pairs can be used following the 1st input to customize the parsing behavior
442475
dat=loadjson(['examples' filesep 'example1.json'],'SimplifyCell',0)
443476
444-
-------------------------------------
445-
savebj.m (saveubjson.m as an alias)
446-
-------------------------------------
477+
% if a URL is provided, loadjson reads JSON data from the URL and return the parsed results,
478+
% similar to webread, except loadjson calls jdatadecode to decode JData annotations
479+
dat=loadjson('https://raw.githubusercontent.com/fangq/jsonlab/master/examples/example1.json')
480+
481+
% using the 'BuildinJSON' flag, one can use the built-in jsondecode.m in MATLAB (R2016+)
482+
% or Octave (7.0+) to parse the JSON data for better speed, note that jsondecode encode
483+
% key names differently compared to loadjson
484+
dat=loadjson('{"_obj":{"string":"value","array":[1,2,3]}}', 'builtinjson', 1)
485+
486+
% when the JSON data contains long key names, one can use 'UseMap' flag to
487+
% request loadjson to store the data in a containers.Map instead of struct (key name limited to 63)
488+
dat=loadjson('{"obj":{"an object with a key longer than 63":"value","array":[1,2,3]}}', 'UseMap', 1)
489+
490+
% loadjson can optionally return a JSON-memory-map object, which defines each JSON element's
491+
% memory buffer offset and length to enable disk-map like fast read/write operations
492+
[dat, mmap]=loadjson('{"obj":{"key":"value","array":[1,2,3]}}')
493+
494+
% if set 'mmaponly' to 1, loadjson only returns the JSON-mmap structure
495+
mmap=loadjson('{"obj":{"key":"value","array":[1,2,3]}}', 'mmaponly', 1)
496+
497+
--------
498+
savebj.m
499+
--------
447500

448501
.. code-block::
449502
503+
% savebj works almost exactly like savejson, except that the output is the more compact binary JSON
450504
a={single(rand(2)), struct('va',1,'vb','string'), 1+2i};
451505
savebj(a)
506+
507+
% customizing the root-name using the 1st input, and the 3rd input setting the output file
452508
savebj('rootname',a,'testdata.ubj')
509+
510+
% like savejson, savebj also allow data compression for even more compact storage
453511
savebj('zeros',zeros(100),'Compression','gzip')
454512
455-
-------------------------------------
456-
loadbj.m (loadubjson.m as an alias)
457-
-------------------------------------
513+
% binary JSON does not need base64-encoding, therefore, the output can be ~33% smaller than text-based JSON
514+
[length(savebj('magic',magic(100),'Compression','zlib')), length(savejson('magic',magic(100),'Compression','zlib'))]
515+
516+
% savebj can output other popular binary JSON formats, such as MessagePack or UBJSON
517+
savebj('mesh',a,'FileName','meshdata.msgpk','MessagePack',1) % same as calling savemsgpack
518+
savebj('mesh',a,'FileName','meshdata.ubj','UBJSON',1) % same as calling saveubjson
519+
520+
--------
521+
loadbj.m
522+
--------
458523

459524
.. code-block::
460525
526+
% similarly, loadbj does almost exactly the same as loadjson, but it parses binary JSON instead
461527
obj=struct('string','value','array',single([1 2 3]),'empty',[],'magic',uint8(magic(5)));
462528
ubjdata=savebj('obj',obj);
529+
530+
% loadbj can load a binary JSON (BJData - a derived format from UBJSON) object from a buffer
463531
dat=loadbj(ubjdata)
532+
533+
% you can test if loadbj parsed object still matches the data saved using savebj
464534
class(dat.obj.array)
465535
isequaln(obj,dat.obj)
536+
537+
% similarly, savebj/loadbj can compress/decompress binary array data using various compressors
466538
dat=loadbj(savebj('',eye(10),'Compression','zlib','CompressArraySize',1))
467539
540+
% if given a path to a binary JSON file (.jdb,.bnii,.pmat,.jmsh,...), it opens and parses the file
541+
dat=loadbj('/path/to/a/binary_json.jdb');
542+
543+
% loadbj can directly load binary JSON data files from URL, here is a binary-JSON based NIfTI file
544+
dat=loadbj('https://neurojson.org/io/stat.cgi?action=get&db=abide&doc=CMU_b&file=0a429cb9101b733f594eefc1261d6985-zlib.bnii')
545+
546+
% similar to loadjson, loadbj can also return JSON-memory-map to permit disk-map
547+
% like direct reading/writing of specific data elements
548+
[dat, mmap]=loadbj(ubjdata)
549+
mmap=loadbj(ubjdata, 'mmaponly', 1)
550+
468551
-------------
469552
jdataencode.m
470553
-------------
471554

472555
.. code-block::
473556
474-
jd=jdataencode(struct('a',rand(5)+1i*rand(5),'b',[],'c',sparse(5,5)))
475-
savejson('',jd)
557+
% jdataencode transforms complex MATLAB data structures (ND-array, sparse array, complex arrays,
558+
% table, graph, containers.Map etc) into JSON-serializable forms using portable JData annotations
559+
% here, we show how to save a complex-valued sparse array using JSON JData annotations
560+
testdata = struct('a',rand(5)+1i*rand(5),'b',[],'c',sparse(5,5));
561+
jd=jdataencode(testdata)
562+
savejson('',jd)
563+
564+
% when setting 'annotatearray' to 1, jdataencode uses _ArrayType_/_ArraySize_/_ArrayData_
565+
% JData tags to store ND array to preserve data types; use 'prefix' to customize variable name prefix
566+
encodedmat=jdataencode(single(magic(5)),'annotatearray',1,'prefix','x')
567+
568+
% when setting 'usearrayshape' to 1, jdataencode can use _ArrayShape_ to encode special matrices
569+
encodedtoeplitz=jdataencode(uint8(toeplitz([1,2,3,4],[1,5,6])),'usearrayshape',1)
476570
477571
-------------
478572
jdatadecode.m
479573
-------------
480574

481575
.. code-block::
482576
483-
rawdata=struct('a',rand(5)+1i*rand(5),'b',[],'c',sparse(5,5));
484-
jd=jdataencode(rawdata)
485-
newjd=jdatadecode(jd)
486-
isequaln(newjd,rawdata)
577+
% jdatadecode does the opposite to jdataencode, it recognizes JData annotations and convert
578+
% those back to MATLAB native data structures, such as ND-arrays, tables, graph etc
579+
rawdata=struct('a',rand(5)+1i*rand(5),'b',[],'c',sparse(5,5));
580+
jd=jdataencode(rawdata)
581+
newjd=jdatadecode(jd)
582+
583+
% we can test that the decoded data are the same as the original
584+
isequaln(newjd,rawdata)
585+
586+
% if one uses jsondecode to parse a JSON object, the output JData annotation name prefix is different
587+
% jsondecode adds "x_" as prefix
588+
rawdecode_builtin = jsondecode(savejson('',rawdata));
589+
rawdecode_builtin.a
590+
finaldecode=jdatadecode(rawdecode_builtin)
591+
592+
% in comparison, loadjson calls encodevarname.m, producing "x0x5F_" as prefix (hex for '_')
593+
% encodevarname encoded names can be reversed to original decodevarname.m
594+
rawdecode_jsonlab = loadjson(savejson('',rawdata), 'jdatadecode', 0);
595+
rawdecode_jsonlab.a
596+
finaldecode=jdatadecode(rawdecode_jsonlab)
597+
598+
--------
599+
savejd.m
600+
--------
601+
602+
.. code-block::
603+
604+
% savejd is a unified interface for savejson/savebj/savemsgpack/saveh5 depending on the output file suffix
605+
a={single(rand(2)), struct('va',1,'vb','string'), 1+2i};
606+
savejd('', a, 'test.json')
607+
savejd('', a, 'test.jdb')
608+
savejd('', a, 'test.ubj')
609+
savejd('', a, 'test.h5')
610+
611+
--------
612+
loadjd.m
613+
--------
614+
615+
.. code-block::
616+
617+
% loadjd is a unified interface for loadjson/loadbj/loadmsgpack/loadh5/load/loadjnifti depending on the input file suffix
618+
% supported types include .json,.jnii,.jdt,.jmsh,.jnirs,.jbids,.bjd,.bnii,.jdb,.bmsh,.bnirs,.ubj,.msgpack,
619+
% .h5,.hdf5,.snirf,.pmat,.nwb,.nii,.nii.gz,.tsv,.tsv.gz,.csv,.csv.gz,.mat,.bvec,.bval; input can be an URL
620+
data = loadjd('test.json');
621+
data = loadjd('test.jdb');
622+
data = loadjd('test.ubj');
623+
data = loadjd('test.h5');
624+
data = loadjd('file:///path/to/test.jnii');
625+
data = loadjd('https://neurojson.org/io/stat.cgi?action=get&db=abide&doc=CMU_b&file=0a429cb9101b733f594eefc1261d6985-zlib.bnii');
626+
627+
---------
628+
jsonget.m
629+
---------
630+
631+
.. code-block::
632+
633+
% loadjson/loadbj JSON-memory-map (mmap) output returned by loadjson or loadbj
634+
% each mmap contains a pair of JSONPath and two numbers [offset, length] of the object in bytes in the buffer/file
635+
jsonstr = '{"obj":{"string":"value","array":[1,2,3]}}';
636+
mmap=loadjson(jsonstr, 'mmaponly', 1)
637+
638+
% mmap = [ ["$",[1,42]], ["$.obj",[8,34]], ["$.obj.string",[18,7]], ["$.obj.array",[34,7]] ]
639+
% this means there are 4 objects, root '$', with its content starting byte 1, with a length of 42 bytes;
640+
% content of object '$.obj' starts byte 8, with a length of 34 bytes
641+
mmap{:}
642+
643+
% using the above mmap, jsonget can return any raw data without needing to reparse jsonstr
644+
% below command returns '[1,2,3]' as a string by following the offset/length data in mmap
645+
jsonget(jsonstr, mmap, '$.obj.array')
646+
647+
% you can request multiple objects by giving multiple JSONPath keys
648+
jsonget(jsonstr, mmap, '$.obj', '$.obj.string')
649+
650+
% you can request multiple objects by giving multiple JSONPath keys
651+
jsonget(jsonstr, mmap, '$.obj', '$.obj.string')
652+
653+
% jsonget not only can fast reading a JSON string buffer, it can also do disk-map read of a file
654+
mmap = loadjson('/path/to/data.json', 'mmaponly', 1);
655+
jsonget('/path/to/data.json', mmap, '$.obj')
656+
657+
---------
658+
jsonset.m
659+
---------
660+
661+
.. code-block::
662+
663+
% using JSON mmap, one can rapidly modify the content of JSON object pointed by a path
664+
jsonstr = '{"obj":{"string":"value","array":[1,2,3]}}';
665+
mmap=loadjson(jsonstr, 'mmaponly', 1)
666+
667+
% we can rewrite object $.obj.array by changing its value '[1,2,3]' to a string "test"
668+
% this returns the updated jsonstr as '{"obj":{"string":"value","array":"test" }}'
669+
% the new value of a key must not have longer bytes than the original value
670+
jsonset(jsonstr, mmap, '$.obj.array', '"test"')
671+
672+
% one can change multiple JSON objects, below returns '{"obj":{"string":"new" ,"array":[] }}'
673+
jsonset(jsonstr, mmap, '$.obj.string', '"new"', '$.obj.array', '[]')
674+
675+
% if mmap is parsed from a file, jsonset can perform disk-map like fast writing to modify the json content
676+
mmap = loadjson('/path/to/data.json', 'mmaponly', 1);
677+
jsonset('/path/to/data.json', mmap, '$.obj.string', '"new"', '$.obj.array', '[]')
487678
488679
---------
489680
examples
@@ -510,6 +701,50 @@ inputs using various encoders and decoders. This unit testing script also serves
510701
a **specification validator** to the JSONLab functions and ensure that the outputs
511702
are compliant to the underlying specifications.
512703

704+
========================================
705+
In-memory data compression/decompression
706+
========================================
707+
708+
JSONLab contains a set of functions to perform in-memory buffer data compression and
709+
decompression
710+
711+
----------------------------------------------------------------------------
712+
Data Compression: {zlib,gzip,base64,lzma,lzip,lz4,lz4hc,zstd,blosc2}encode.m
713+
----------------------------------------------------------------------------
714+
715+
.. code-block::
716+
717+
% MATLAB running with jvm provides zlib and gzip compression natively
718+
% one can also install ZMat (https://github.com/NeuroJSON/zmat) to do zlib(.zip) or gzip (.gz) compression
719+
output = zlibencode(diag([1,2,3,4]))
720+
[output, info] = zlibencode(uint8(magic(8)))
721+
outputbase64 = char(base64encode(output(:)))
722+
[output, info] = gzipencode(uint8(magic(8)))
723+
% setting a negative integer between -1 to -9 to set compression level: -9 being the highest
724+
[output, info] = zlibencode(uint8(magic(8)), -9)
725+
726+
% other advanced compressions are supported but requires ZMat
727+
% lzma offers the highest compression rate, but slow compresison speed
728+
output = lzmaencode(uint8(magic(8)))
729+
730+
% lz4 offers the fastest compression speed, but slightly low compression ratio
731+
output = lz4encode(peaks(10))
732+
output = lz4hcencode(uint8(magic(8)))
733+
734+
% zstd has a good balanced speed/ratio, similar to zlib
735+
output = zstdencode(peaks(10))
736+
output = zstdencode(peaks(10), -9)
737+
738+
-----------------------------------------------------------------------------
739+
Data Deompression: {zlib,gzip,base64,lzma,lzip,lz4,lz4hc,zstd,blosc2}decode.m
740+
-----------------------------------------------------------------------------
741+
742+
.. code-block::
743+
744+
[compressed, info] = zlibencode(eye(10));
745+
decompressd = zlibdecode(compressed);
746+
decompressd = zlibdecode(compressed, info);
747+
513748
514749
========================================
515750
Using ``jsave/jload`` to share workspace

0 commit comments

Comments
 (0)