@@ -396,7 +396,7 @@ In JSONLab 2.9.8 and later versions, a unified file loading and saving interface
396396is provided for JSON, binary JSON and HDF5, including ``loadjd `` and ``savejd ``
397397for 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
408408In 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----------
412412savejson.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-------------
469552jdataencode.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-------------
478572jdatadecode.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---------
489680examples
@@ -510,6 +701,50 @@ inputs using various encoders and decoders. This unit testing script also serves
510701a **specification validator ** to the JSONLab functions and ensure that the outputs
511702are 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========================================
515750Using ``jsave/jload `` to share workspace
0 commit comments