├── .gitattributes ├── .github └── workflows │ ├── matlab.yml │ └── octave.yml ├── .gitignore ├── LICENSE ├── README.md ├── jsmn.c ├── jsmn.h ├── jsonread.c ├── jsonread.m ├── jsonread.mexa64 ├── jsonread.mexmaca64 ├── jsonread.mexmaci64 ├── jsonread.mexw64 ├── jsonwrite.m └── tests ├── test_jsonread.m └── test_jsonwrite.m /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.m text diff=matlab 4 | *.c text 5 | *.cpp text 6 | *.h text 7 | *.md text 8 | 9 | *.fig binary 10 | *.mat binary 11 | *.mex binary 12 | *.mexa64 binary 13 | *.mexw64 binary 14 | *.mexmaci64 binary 15 | *.mexmaca64 binary 16 | *.mlapp binary 17 | *.mlx binary 18 | *.p binary 19 | -------------------------------------------------------------------------------- /.github/workflows/matlab.yml: -------------------------------------------------------------------------------- 1 | name: MATLAB Tests 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | pull_request: 7 | workflow_dispatch: 8 | jobs: 9 | matlab-test: 10 | name: MATLAB Tests 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | version: ["R2020a", "R2020b", "R2021a", "R2021b", "R2022a", "R2022b", "R2023a", "R2023b"] 15 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 16 | exclude: 17 | - os: windows-latest 18 | version: "R2020a" # MATLAB not available 19 | - os: windows-latest 20 | version: "R2020b" # MATLAB not available 21 | - os: windows-latest 22 | version: "R2021a" # Compiler not available 23 | - os: windows-latest 24 | version: "R2021b" # Compiler not available 25 | runs-on: ${{matrix.os}} 26 | steps: 27 | - name: Check out repository 28 | uses: actions/checkout@v3 29 | - name: Set up MATLAB 30 | uses: matlab-actions/setup-matlab@v1 31 | with: 32 | release: ${{matrix.version}} 33 | - name: Run tests with existing MEX files 34 | uses: matlab-actions/run-command@v1 35 | with: 36 | command: addpath(pwd); results = runtests('tests'), assertSuccess(results); 37 | - name: Compile MEX file 38 | run: mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS 39 | - name: Run tests with newly compiled MEX files 40 | uses: matlab-actions/run-command@v1 41 | with: 42 | command: addpath(pwd); results = runtests('tests'), assertSuccess(results); 43 | - name: Get MEX extension on Linux/macOS 44 | if: runner.os != 'Windows' 45 | run: echo "MEXEXT=$(mexext)" >> $GITHUB_ENV 46 | - name: Get MEX extension on Windows 47 | if: runner.os == 'Windows' 48 | run: echo "MEXEXT=$(mexext.bat)" >> $env:GITHUB_ENV 49 | - name: Upload Artifact 50 | uses: actions/upload-artifact@v3 51 | with: 52 | name: jsonio-mex-${{runner.os}}-${{matrix.version}} 53 | path: ./**/*.${{env.MEXEXT}} 54 | retention-days: 5 55 | -------------------------------------------------------------------------------- /.github/workflows/octave.yml: -------------------------------------------------------------------------------- 1 | name: Octave Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | linux: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v3 16 | 17 | - name: Install GNU Octave 18 | run: | 19 | sudo apt-get update && sudo apt-get install -y \ 20 | octave liboctave-dev && \ 21 | octave --version 22 | - name: Compile JSONio MEX files 23 | run: mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS 24 | 25 | - name: Install JSONio library in GNU Octave 26 | run: octave --no-gui --no-window-system --silent --eval "addpath (pwd); savepath ();" 27 | 28 | - name: Run tests 29 | run: octave --no-gui --no-window-system --silent --eval "disp ('Disabled.')" 30 | 31 | windows: 32 | runs-on: windows-latest 33 | steps: 34 | - name: Check out code 35 | uses: actions/checkout@v3 36 | 37 | - name: Install GNU Octave 38 | run: | 39 | choco install octave.portable 40 | octave --version 41 | 42 | - name: Compile JSONio MEX files 43 | run: octave --no-gui --no-window-system --silent --eval "mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS" 44 | 45 | - name: Install JSONio library in GNU Octave 46 | run: octave --no-gui --no-window-system --silent --eval "addpath (pwd); savepath ();" 47 | 48 | - name: Run tests 49 | run: octave --no-gui --no-window-system --silent --eval "disp ('Disabled.')" 50 | 51 | macos: 52 | runs-on: macos-latest 53 | steps: 54 | - name: Check out code 55 | uses: actions/checkout@v3 56 | 57 | - name: Install GNU Octave 58 | run: | 59 | brew install octave # https://formulae.brew.sh/formula/octave 60 | octave --version 61 | 62 | - name: Compile JSONio MEX files 63 | run: octave --no-gui --no-window-system --silent --eval "mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS" 64 | 65 | - name: Install JSONio library in GNU Octave 66 | run: octave --no-gui --no-window-system --silent --eval "addpath (pwd); savepath ();" 67 | 68 | - name: Run tests 69 | run: octave --no-gui --no-window-system --silent --eval "disp ('Disabled.')" 70 | 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.asv 2 | *.o 3 | *.mex 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-2023 Guillaume Flandin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONio: a MATLAB/Octave JSON library 2 | 3 | ![GitHub](https://img.shields.io/github/license/gllmflndn/JSONio) 4 | [![MATLAB](https://github.com/gllmflndn/JSONio/actions/workflows/matlab.yml/badge.svg)](https://github.com/gllmflndn/JSONio/actions) 5 | [![Octave](https://github.com/gllmflndn/JSONio/actions/workflows/octave.yml/badge.svg)](https://github.com/gllmflndn/JSONio/actions) 6 | 7 | JSONio is a MATLAB/Octave library to read/write data in the JSON (JavaScript Object Notation) data-interchange format. 8 | 9 | * JSON: https://www.json.org/ 10 | 11 | It relies on the excellent JSON parser `jsmn` written by [Serge Zaitsev](https://zserge.com/): 12 | 13 | * jsmn: https://zserge.com/jsmn/ 14 | 15 | ## INSTALLATION 16 | 17 | Simply add the JSONio directory to the MATLAB path: 18 | 19 | ```matlab 20 | addpath /home/login/Documents/MATLAB/JSONio 21 | ``` 22 | 23 | A compiled MEX file is provided for 64-bit MATLAB platforms. It needs to be compiled for Octave with: 24 | ``` 25 | mkoctfile --mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS 26 | ``` 27 | 28 | ## EXAMPLE 29 | 30 | ```matlab 31 | json = jsonread(filename) 32 | 33 | jsonwrite(filename, json) 34 | ``` 35 | -------------------------------------------------------------------------------- /jsmn.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2010 Serge A. Zaitsev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | 24 | #include 25 | 26 | #include "jsmn.h" 27 | 28 | /** 29 | * Allocates a fresh unused token from the token pull. 30 | */ 31 | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, 32 | jsmntok_t *tokens, size_t num_tokens) { 33 | jsmntok_t *tok; 34 | if (parser->toknext >= num_tokens) { 35 | return NULL; 36 | } 37 | tok = &tokens[parser->toknext++]; 38 | tok->start = tok->end = -1; 39 | tok->size = 0; 40 | #ifdef JSMN_PARENT_LINKS 41 | tok->parent = -1; 42 | #endif 43 | return tok; 44 | } 45 | 46 | /** 47 | * Fills token type and boundaries. 48 | */ 49 | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, 50 | int start, int end) { 51 | token->type = type; 52 | token->start = start; 53 | token->end = end; 54 | token->size = 0; 55 | } 56 | 57 | /** 58 | * Fills next available token with JSON primitive. 59 | */ 60 | static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, 61 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 62 | jsmntok_t *token; 63 | int start; 64 | 65 | start = parser->pos; 66 | 67 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 68 | switch (js[parser->pos]) { 69 | #ifndef JSMN_STRICT 70 | /* In strict mode primitive must be followed by "," or "}" or "]" */ 71 | case ':': 72 | #endif 73 | case '\t' : case '\r' : case '\n' : case ' ' : 74 | case ',' : case ']' : case '}' : 75 | goto found; 76 | } 77 | if (js[parser->pos] < 32 || js[parser->pos] >= 127) { 78 | parser->pos = start; 79 | return JSMN_ERROR_INVAL; 80 | } 81 | } 82 | #ifdef JSMN_STRICT 83 | /* In strict mode primitive must be followed by a comma/object/array */ 84 | parser->pos = start; 85 | return JSMN_ERROR_PART; 86 | #endif 87 | 88 | found: 89 | if (tokens == NULL) { 90 | parser->pos--; 91 | return 0; 92 | } 93 | token = jsmn_alloc_token(parser, tokens, num_tokens); 94 | if (token == NULL) { 95 | parser->pos = start; 96 | return JSMN_ERROR_NOMEM; 97 | } 98 | jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); 99 | #ifdef JSMN_PARENT_LINKS 100 | token->parent = parser->toksuper; 101 | #endif 102 | parser->pos--; 103 | return 0; 104 | } 105 | 106 | /** 107 | * Filsl next token with JSON string. 108 | */ 109 | static int jsmn_parse_string(jsmn_parser *parser, const char *js, 110 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 111 | jsmntok_t *token; 112 | 113 | int start = parser->pos; 114 | 115 | parser->pos++; 116 | 117 | /* Skip starting quote */ 118 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 119 | char c = js[parser->pos]; 120 | 121 | /* Quote: end of string */ 122 | if (c == '\"') { 123 | if (tokens == NULL) { 124 | return 0; 125 | } 126 | token = jsmn_alloc_token(parser, tokens, num_tokens); 127 | if (token == NULL) { 128 | parser->pos = start; 129 | return JSMN_ERROR_NOMEM; 130 | } 131 | jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); 132 | #ifdef JSMN_PARENT_LINKS 133 | token->parent = parser->toksuper; 134 | #endif 135 | return 0; 136 | } 137 | 138 | /* Backslash: Quoted symbol expected */ 139 | if (c == '\\' && parser->pos + 1 < len) { 140 | int i; 141 | parser->pos++; 142 | switch (js[parser->pos]) { 143 | /* Allowed escaped symbols */ 144 | case '\"': case '/' : case '\\' : case 'b' : 145 | case 'f' : case 'r' : case 'n' : case 't' : 146 | break; 147 | /* Allows escaped symbol \uXXXX */ 148 | case 'u': 149 | parser->pos++; 150 | for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { 151 | /* If it isn't a hex character we have an error */ 152 | if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ 153 | (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ 154 | (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ 155 | parser->pos = start; 156 | return JSMN_ERROR_INVAL; 157 | } 158 | parser->pos++; 159 | } 160 | parser->pos--; 161 | break; 162 | /* Unexpected symbol */ 163 | default: 164 | parser->pos = start; 165 | return JSMN_ERROR_INVAL; 166 | } 167 | } 168 | } 169 | parser->pos = start; 170 | return JSMN_ERROR_PART; 171 | } 172 | 173 | /** 174 | * Parse JSON string and fill tokens. 175 | */ 176 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 177 | jsmntok_t *tokens, unsigned int num_tokens) { 178 | int r; 179 | int i; 180 | jsmntok_t *token; 181 | int count = parser->toknext; 182 | 183 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 184 | char c; 185 | jsmntype_t type; 186 | 187 | c = js[parser->pos]; 188 | switch (c) { 189 | case '{': case '[': 190 | count++; 191 | if (tokens == NULL) { 192 | break; 193 | } 194 | token = jsmn_alloc_token(parser, tokens, num_tokens); 195 | if (token == NULL) 196 | return JSMN_ERROR_NOMEM; 197 | if (parser->toksuper != -1) { 198 | tokens[parser->toksuper].size++; 199 | #ifdef JSMN_PARENT_LINKS 200 | token->parent = parser->toksuper; 201 | #endif 202 | } 203 | token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); 204 | token->start = parser->pos; 205 | parser->toksuper = parser->toknext - 1; 206 | break; 207 | case '}': case ']': 208 | if (tokens == NULL) 209 | break; 210 | type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); 211 | #ifdef JSMN_PARENT_LINKS 212 | if (parser->toknext < 1) { 213 | return JSMN_ERROR_INVAL; 214 | } 215 | token = &tokens[parser->toknext - 1]; 216 | for (;;) { 217 | if (token->start != -1 && token->end == -1) { 218 | if (token->type != type) { 219 | return JSMN_ERROR_INVAL; 220 | } 221 | token->end = parser->pos + 1; 222 | parser->toksuper = token->parent; 223 | break; 224 | } 225 | if (token->parent == -1) { 226 | break; 227 | } 228 | token = &tokens[token->parent]; 229 | } 230 | #else 231 | for (i = parser->toknext - 1; i >= 0; i--) { 232 | token = &tokens[i]; 233 | if (token->start != -1 && token->end == -1) { 234 | if (token->type != type) { 235 | return JSMN_ERROR_INVAL; 236 | } 237 | parser->toksuper = -1; 238 | token->end = parser->pos + 1; 239 | break; 240 | } 241 | } 242 | /* Error if unmatched closing bracket */ 243 | if (i == -1) return JSMN_ERROR_INVAL; 244 | for (; i >= 0; i--) { 245 | token = &tokens[i]; 246 | if (token->start != -1 && token->end == -1) { 247 | parser->toksuper = i; 248 | break; 249 | } 250 | } 251 | #endif 252 | break; 253 | case '\"': 254 | r = jsmn_parse_string(parser, js, len, tokens, num_tokens); 255 | if (r < 0) return r; 256 | count++; 257 | if (parser->toksuper != -1 && tokens != NULL) 258 | tokens[parser->toksuper].size++; 259 | break; 260 | case '\t' : case '\r' : case '\n' : case ' ': 261 | break; 262 | case ':': 263 | parser->toksuper = parser->toknext - 1; 264 | break; 265 | case ',': 266 | if (tokens != NULL && parser->toksuper != -1 && 267 | tokens[parser->toksuper].type != JSMN_ARRAY && 268 | tokens[parser->toksuper].type != JSMN_OBJECT) { 269 | #ifdef JSMN_PARENT_LINKS 270 | parser->toksuper = tokens[parser->toksuper].parent; 271 | #else 272 | for (i = parser->toknext - 1; i >= 0; i--) { 273 | if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { 274 | if (tokens[i].start != -1 && tokens[i].end == -1) { 275 | parser->toksuper = i; 276 | break; 277 | } 278 | } 279 | } 280 | #endif 281 | } 282 | break; 283 | #ifdef JSMN_STRICT 284 | /* In strict mode primitives are: numbers and booleans */ 285 | case '-': case '0': case '1' : case '2': case '3' : case '4': 286 | case '5': case '6': case '7' : case '8': case '9': 287 | case 't': case 'f': case 'n' : 288 | /* And they must not be keys of the object */ 289 | if (tokens != NULL && parser->toksuper != -1) { 290 | jsmntok_t *t = &tokens[parser->toksuper]; 291 | if (t->type == JSMN_OBJECT || 292 | (t->type == JSMN_STRING && t->size != 0)) { 293 | return JSMN_ERROR_INVAL; 294 | } 295 | } 296 | #else 297 | /* In non-strict mode every unquoted value is a primitive */ 298 | default: 299 | #endif 300 | r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); 301 | if (r < 0) return r; 302 | count++; 303 | if (parser->toksuper != -1 && tokens != NULL) 304 | tokens[parser->toksuper].size++; 305 | break; 306 | 307 | #ifdef JSMN_STRICT 308 | /* Unexpected char in strict mode */ 309 | default: 310 | return JSMN_ERROR_INVAL; 311 | #endif 312 | } 313 | } 314 | 315 | if (tokens != NULL) { 316 | for (i = parser->toknext - 1; i >= 0; i--) { 317 | /* Unmatched opened object or array */ 318 | if (tokens[i].start != -1 && tokens[i].end == -1) { 319 | return JSMN_ERROR_PART; 320 | } 321 | } 322 | } 323 | 324 | return count; 325 | } 326 | 327 | /** 328 | * Creates a new parser based over a given buffer with an array of tokens 329 | * available. 330 | */ 331 | void jsmn_init(jsmn_parser *parser) { 332 | parser->pos = 0; 333 | parser->toknext = 0; 334 | parser->toksuper = -1; 335 | } 336 | 337 | -------------------------------------------------------------------------------- /jsmn.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2010 Serge A. Zaitsev 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | 24 | #ifndef __JSMN_H_ 25 | #define __JSMN_H_ 26 | 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | /** 34 | * JSON type identifier. Basic types are: 35 | * o Object 36 | * o Array 37 | * o String 38 | * o Other primitive: number, boolean (true/false) or null 39 | */ 40 | typedef enum { 41 | JSMN_UNDEFINED = 0, 42 | JSMN_OBJECT = 1, 43 | JSMN_ARRAY = 2, 44 | JSMN_STRING = 3, 45 | JSMN_PRIMITIVE = 4 46 | } jsmntype_t; 47 | 48 | enum jsmnerr { 49 | /* Not enough tokens were provided */ 50 | JSMN_ERROR_NOMEM = -1, 51 | /* Invalid character inside JSON string */ 52 | JSMN_ERROR_INVAL = -2, 53 | /* The string is not a full JSON packet, more bytes expected */ 54 | JSMN_ERROR_PART = -3 55 | }; 56 | 57 | /** 58 | * JSON token description. 59 | * @param type type (object, array, string etc.) 60 | * @param start start position in JSON data string 61 | * @param end end position in JSON data string 62 | */ 63 | typedef struct { 64 | jsmntype_t type; 65 | int start; 66 | int end; 67 | int size; 68 | #ifdef JSMN_PARENT_LINKS 69 | int parent; 70 | #endif 71 | } jsmntok_t; 72 | 73 | /** 74 | * JSON parser. Contains an array of token blocks available. Also stores 75 | * the string being parsed now and current position in that string 76 | */ 77 | typedef struct { 78 | unsigned int pos; /* offset in the JSON string */ 79 | unsigned int toknext; /* next token to allocate */ 80 | int toksuper; /* superior token node, e.g parent object or array */ 81 | } jsmn_parser; 82 | 83 | /** 84 | * Create JSON parser over an array of tokens 85 | */ 86 | void jsmn_init(jsmn_parser *parser); 87 | 88 | /** 89 | * Run JSON parser. It parses a JSON data string into and array of tokens, each describing 90 | * a single JSON object. 91 | */ 92 | int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 93 | jsmntok_t *tokens, unsigned int num_tokens); 94 | 95 | #ifdef __cplusplus 96 | } 97 | #endif 98 | 99 | #endif /* __JSMN_H_ */ 100 | -------------------------------------------------------------------------------- /jsonread.c: -------------------------------------------------------------------------------- 1 | /* 2 | * JSONio: https://www.gllmflndn.com/software/matlab/jsonio/ 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include "jsmn.h" 9 | #include "mex.h" 10 | 11 | /* for MATLAB <= R2008a or GNU Octave */ 12 | /* 13 | #if defined(mxSetLogical) || !defined(MATLAB_MEX_FILE) 14 | mxArray *mexCallMATLABWithTrap(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[], const char *functionName) { 15 | mxArray *mx = NULL; 16 | const char **fields = (const char *[]){"identifier", "message", "case", "stack"}; 17 | mexSetTrapFlag(1); 18 | if (mexCallMATLAB(nlhs, plhs, nrhs, prhs, functionName)) { 19 | mx = mxCreateStructMatrix(1, 1, 4, fields); 20 | mxSetFieldByNumber(mx, 0, 0, mxCreateString("MATLAB:error")); 21 | mxSetFieldByNumber(mx, 0, 1, mxCreateString(functionName)); 22 | mxSetFieldByNumber(mx, 0, 2, mxCreateCellMatrix(0, 0)); 23 | mxSetFieldByNumber(mx, 0, 3, mxCreateStructMatrix(0, 1, 0, NULL)); 24 | return mx; 25 | } 26 | else { 27 | return NULL; 28 | } 29 | } 30 | #endif 31 | */ 32 | 33 | #if defined(_WIN32) || defined(_WIN64) 34 | #define strcasecmp _stricmp 35 | #endif 36 | 37 | static enum jsonrepsty { 38 | JSON_REPLACEMENT_STYLE_NOP, 39 | JSON_REPLACEMENT_STYLE_UNDERSCORE, 40 | JSON_REPLACEMENT_STYLE_HEX, 41 | JSON_REPLACEMENT_STYLE_DELETE 42 | } ReplacementStyle; 43 | 44 | static int should_convert_to_array(const mxArray *pm) { 45 | size_t i, j, n, nfields; 46 | mxClassID cat; 47 | mwSize ndims, *dim, *d; 48 | 49 | if (!mxIsCell(pm)) { 50 | return 0; 51 | } 52 | n = mxGetNumberOfElements(pm); 53 | if (n) { 54 | cat = mxGetClassID(mxGetCell(pm, 0)); 55 | ndims = mxGetNumberOfDimensions(mxGetCell(pm, 0)); 56 | dim = (mwSize *)mxGetDimensions(mxGetCell(pm, 0)); 57 | } 58 | else { 59 | return 1; 60 | } 61 | switch (cat) { 62 | case mxSTRUCT_CLASS: 63 | case mxDOUBLE_CLASS: 64 | case mxLOGICAL_CLASS: 65 | break; 66 | default: 67 | return 0; 68 | } 69 | for (i = 1; i < n; i++) { 70 | if (cat != mxGetClassID(mxGetCell(pm, i))) { 71 | return 0; 72 | } 73 | else if (ndims != mxGetNumberOfDimensions(mxGetCell(pm, i))) { 74 | return 0; 75 | } 76 | d = (mwSize *)mxGetDimensions(mxGetCell(pm, i)); 77 | for (j = 0; j < ndims; j++) { 78 | if (dim[j] != d[j]) { 79 | if ( !( (cat == mxDOUBLE_CLASS) 80 | && (mxGetNumberOfElements(mxGetCell(pm, i)) <= 1) 81 | && (mxGetNumberOfElements(mxGetCell(pm, 0)) <= 1) ) ) { 82 | return 0; 83 | } 84 | } 85 | } 86 | if (cat == mxSTRUCT_CLASS) { 87 | nfields = mxGetNumberOfFields(mxGetCell(pm, i)); 88 | if (nfields != mxGetNumberOfFields(mxGetCell(pm, 0))) { 89 | return 0; 90 | } 91 | for (j = 0; j < nfields; j++) { 92 | if (mxGetFieldNumber(mxGetCell(pm, 0), mxGetFieldNameByNumber(mxGetCell(pm, i), j)) != j) { 93 | return 0; 94 | } 95 | } 96 | } 97 | } 98 | if (cat == mxDOUBLE_CLASS) { 99 | for (i = 0; i < n; i++) { 100 | if (mxGetNumberOfElements(mxGetCell(pm, i)) == 0) { 101 | mxDestroyArray(mxGetCell(pm, i)); 102 | mxSetCell((mxArray *)pm, i, mxCreateDoubleScalar(mxGetNaN())); 103 | } 104 | } 105 | } 106 | return 1; 107 | } 108 | 109 | static int setup_for_cell2mat(mxArray *pm) { 110 | size_t i, n, a, b; 111 | mxClassID cat; 112 | mxArray *cell; 113 | mwSize d, *olddim = NULL, *newdim = NULL; 114 | 115 | n = mxGetNumberOfElements(pm); 116 | if (!n) { 117 | return 0; 118 | } 119 | cell = mxGetCell(pm, 0); 120 | cat = mxGetClassID(cell); 121 | if ((cat != mxDOUBLE_CLASS) && (cat != mxLOGICAL_CLASS)) { 122 | return 0; 123 | } 124 | if ((mxGetNumberOfDimensions(cell) == 2) && (mxGetM(cell) == 1)) { 125 | return 0; 126 | } 127 | if ((mxGetNumberOfDimensions(cell) == 2) && (mxGetN(cell) == 1)) { 128 | mxSetN(pm, mxGetM(pm)); 129 | mxSetM(pm, 1); 130 | } 131 | else { 132 | d = mxGetNumberOfDimensions(pm); 133 | olddim = (mwSize *)mxGetDimensions(pm); 134 | newdim = mxMalloc((d+1) * sizeof(*newdim)); 135 | for (i=0;i= 'a') && (re[1] <= 'z')) { 200 | re[1] -= 32; /* 'a'-'A' */ 201 | } 202 | re++; 203 | } 204 | else { 205 | if ( ((re[0] >= '0') && (re[0] <= '9')) 206 | || ((re[0] >= 'a') && (re[0] <= 'z')) 207 | || ((re[0] >= 'A') && (re[0] <= 'Z'))) { 208 | if (re != wr) { wr[0] = re[0]; } 209 | wr++; 210 | } 211 | else { 212 | if (ReplacementStyle == JSON_REPLACEMENT_STYLE_UNDERSCORE) { 213 | wr[0] = '_'; wr++; 214 | } 215 | } 216 | re++; 217 | beg = 0; 218 | } 219 | } 220 | wr[0] = '\0'; 221 | if ( (field[0] == '\0') || (field[0] == '_') 222 | || ((field[0] >= '0') && (field[0] <= '9')) ) { 223 | field = field - 1; 224 | field[0] = 'x'; 225 | } 226 | return field; 227 | } 228 | 229 | static char * valid_fieldname_hex(char *field) { 230 | /* matlab.lang.makeValidName 231 | with ReplacementStyle == 'hex' and Prefix == 'x' */ 232 | char *re = field, *wr = NULL, *ret = NULL, *str = NULL; 233 | int sts, beg = 1; 234 | size_t len = 4*strlen(field)+2; /* 'x' + all '0x??' + \0 */ 235 | mxArray *mx = NULL, *ma = NULL; 236 | ret = (char *)malloc(len); 237 | wr = ret; 238 | if (! (((re[0] >= 'a') && (re[0] <= 'z')) 239 | || ((re[0] >= 'A') && (re[0] <= 'Z'))) ) { 240 | wr[0] = 'x'; wr++; 241 | } 242 | while (re[0] != '\0') { 243 | if ((re[0] == 32) || (re[0] == 9)) { /* ' ' or \t */ 244 | if (re[1] == '\0') { 245 | break; 246 | } 247 | else if ((beg != 1) && (re[1] >= 'a') && (re[1] <= 'z')) { 248 | re[1] -= 32; /* 'a'-'A' */ 249 | } 250 | re++; 251 | } 252 | else { 253 | if ( ((re[0] >= 'a') && (re[0] <= 'z')) 254 | || ((re[0] >= 'A') && (re[0] <= 'Z')) 255 | || ((((re[0] >= '0') && (re[0] <= '9')) 256 | || (re[0] == '_')) && !beg) ) { 257 | wr[0] = re[0]; wr++; 258 | } 259 | else { 260 | /* could also use sprintf(wr,"%02X",re[0]) 261 | but might call unicode2native in the future */ 262 | wr[0] = '0'; wr++; 263 | wr[0] = 'x'; wr++; 264 | wr[0] = re[0]; 265 | wr[1] = '\0'; 266 | ma = mxCreateString((const char *)wr); 267 | sts = mexCallMATLAB(1, &mx, 1, &ma, "dec2hex"); 268 | if (sts != 0) { 269 | mexErrMsgTxt("Cannot convert to hexadecimal representation."); 270 | } 271 | mxDestroyArray(ma); 272 | str = mxArrayToString(mx); 273 | mxDestroyArray(mx); 274 | wr[0] = str[0]; wr++; 275 | wr[0] = str[1]; wr++; 276 | mxFree(str); 277 | } 278 | re++; 279 | beg = 0; 280 | } 281 | } 282 | wr[0] = '\0'; 283 | return ret; 284 | } 285 | 286 | static char * valid_fieldname(char *field, int *need_free) { 287 | switch (ReplacementStyle) { 288 | case JSON_REPLACEMENT_STYLE_NOP: 289 | /* nop */ 290 | *need_free = 0; 291 | break; 292 | case JSON_REPLACEMENT_STYLE_UNDERSCORE: 293 | case JSON_REPLACEMENT_STYLE_DELETE: 294 | field = valid_fieldname_underscore(field); 295 | *need_free = 0; 296 | break; 297 | case JSON_REPLACEMENT_STYLE_HEX: 298 | field = valid_fieldname_hex(field); 299 | *need_free = 1; 300 | break; 301 | default: 302 | mexErrMsgTxt("Unknown ReplacementStyle."); 303 | break; 304 | } 305 | return field; 306 | } 307 | 308 | static int primitive(char *js, jsmntok_t *tok, mxArray **mx) { 309 | mxArray *ma = NULL; 310 | int sts; 311 | switch (js[tok->start]) { 312 | case 't' : 313 | *mx = mxCreateLogicalScalar(1); 314 | break; 315 | case 'f' : 316 | *mx = mxCreateLogicalScalar(0); 317 | break; 318 | case 'n' : 319 | *mx = mxCreateDoubleMatrix(0,0,mxREAL); 320 | break; 321 | default: /* '-', '0'..'9' */ 322 | ma = mxCreateString(get_string(js, tok->start, tok->end)); 323 | sts = mexCallMATLAB(1, mx, 1, &ma, "str2double"); 324 | if (sts != 0) { 325 | mexErrMsgTxt("Conversion from string to double failed."); 326 | } 327 | mxDestroyArray(ma); 328 | break; 329 | } 330 | return 1; 331 | } 332 | 333 | static int value(char *js, jsmntok_t *tok, mxArray **mx) { 334 | *mx = mxCreateString(get_string(js, tok->start, tok->end)); 335 | return 1; 336 | } 337 | 338 | static int array(char *js, jsmntok_t *tok, mxArray **mx) { 339 | int i, j; 340 | mxArray *ma = NULL; 341 | mxArray *array[1], *parray[2]; 342 | mwSize d; 343 | int perm, sts; 344 | 345 | *mx = mxCreateCellMatrix(tok->size, 1); 346 | for (i = 0, j = 0; i < tok->size; i++) { 347 | j += create_struct(js, tok+1+j, &ma); 348 | mxSetCell(*mx, i, ma); 349 | } 350 | 351 | /* Convert cell array into array when required */ 352 | if (should_convert_to_array(*mx)) { 353 | perm = setup_for_cell2mat(*mx); 354 | sts = mexCallMATLAB(1, array, 1, mx, "cell2mat"); 355 | if (sts == 0) { 356 | mxDestroyArray(*mx); 357 | *mx = *array; 358 | 359 | if (perm) { 360 | d = mxGetNumberOfDimensions(*mx); 361 | parray[0] = *mx; 362 | parray[1] = mxCreateNumericMatrix(1, d, mxDOUBLE_CLASS, mxREAL); 363 | mxGetPr(parray[1])[0] = d; 364 | for (i=1;isize == 0) { 386 | *mx = mxCreateStructMatrix(1, 1, 0, NULL); 387 | return 1; 388 | } 389 | for (i = 0, j = 0, k = 0; i < tok->size; i++) { 390 | if ((tok+1+j)->type != JSMN_STRING) 391 | mexErrMsgTxt("Ill-formatted JSON."); 392 | field = get_string(js, (tok+1+j)->start, (tok+1+j)->end); 393 | field = valid_fieldname(field, &need_free); 394 | j++; 395 | if (i == 0) { 396 | *mx = mxCreateStructMatrix(1, 1, 1, (const char**)&field); 397 | } 398 | else { 399 | k = mxGetFieldNumber(*mx, field); 400 | if (k != -1) { 401 | mexWarnMsgTxt("Duplicate key."); 402 | ma = mxGetFieldByNumber(*mx, 0, k); 403 | mxRemoveField(*mx, k); 404 | mxDestroyArray(ma); 405 | } 406 | k = mxAddField(*mx, field); 407 | if (k == -1) 408 | mexErrMsgTxt("mxAddField()"); 409 | } 410 | if (need_free) { free(field); } 411 | j += create_struct(js, tok+1+j, &ma); 412 | mxSetFieldByNumber(*mx, 0, k, ma); 413 | } 414 | return j+1; 415 | } 416 | 417 | static int create_struct(char *js, jsmntok_t *tok, mxArray **mx) { 418 | if (tok->type == JSMN_PRIMITIVE) { 419 | return primitive(js, tok, mx); 420 | } else if (tok->type == JSMN_STRING) { 421 | return value(js, tok, mx); 422 | } else if (tok->type == JSMN_OBJECT) { 423 | return object(js, tok, mx); 424 | } else if (tok->type == JSMN_ARRAY) { 425 | return array(js, tok, mx); 426 | } 427 | return 0; 428 | } 429 | 430 | static jsmntok_t * parse(const char *js, size_t jslen) { 431 | int r; 432 | jsmn_parser p; 433 | jsmntok_t *tok = NULL; 434 | size_t tokcount = 2; 435 | 436 | jsmn_init(&p); 437 | tok = mxMalloc(sizeof(*tok) * tokcount); 438 | if (tok == NULL) { 439 | mexErrMsgTxt("mxMalloc()"); 440 | } 441 | 442 | for (;;) { 443 | r = jsmn_parse(&p, js, jslen, tok, tokcount); 444 | if (r < 0) { 445 | if (r == JSMN_ERROR_NOMEM) { 446 | tokcount = tokcount * 2; 447 | tok = mxRealloc(tok, sizeof(*tok) * tokcount); 448 | if (tok == NULL) { 449 | mexErrMsgTxt("mxRealloc()"); 450 | } 451 | } 452 | else if ((r == JSMN_ERROR_INVAL) || (r == JSMN_ERROR_PART)) { 453 | mexErrMsgTxt("Invalid or incomplete JSON."); 454 | } 455 | else { 456 | mexErrMsgTxt("Unknown JSON parsing error."); 457 | } 458 | } 459 | else { 460 | break; 461 | } 462 | } 463 | 464 | return tok; 465 | } 466 | 467 | static char * get_data(const mxArray * mx, size_t * jslen) { 468 | /* should attempt to minimise copy */ 469 | int i, filename, sts; 470 | mxArray *ma = NULL; 471 | char *js = NULL; 472 | 473 | js = mxArrayToString(mx); 474 | if (js == NULL) { 475 | mexErrMsgTxt("mxArrayToString()"); 476 | } 477 | *jslen = strlen(js); 478 | if (*jslen == 0) 479 | mexErrMsgTxt("Empty JSON."); 480 | 481 | /* detect whether input string is a filename */ 482 | for (i = 0, filename = 1; i < *jslen; i++) { 483 | if ((js[i] == '{') || (js[i] == '[')) { 484 | filename = 0; 485 | break; 486 | } 487 | } 488 | if (filename == 1) { 489 | mxFree(js); 490 | sts = mexCallMATLAB(1, &ma, 1, (mxArray **)&mx, "fileread"); 491 | if (sts != 0) { 492 | mexErrMsgTxt("Cannot read JSON file."); 493 | } 494 | js = mxArrayToString(ma); 495 | if (js == NULL) { 496 | mexErrMsgTxt("mxArrayToString()"); 497 | } 498 | mxDestroyArray(ma); 499 | *jslen = strlen(js); 500 | } 501 | return js; 502 | } 503 | 504 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { 505 | char *js = NULL; 506 | const char *field = NULL; 507 | size_t i, jslen = 0, nfields; 508 | jsmntok_t *tok = NULL; 509 | mxArray *mx = NULL; 510 | char *repsty = NULL; 511 | 512 | /* Validate input arguments */ 513 | if (nrhs == 0) { 514 | mexErrMsgTxt("Not enough input arguments."); 515 | } 516 | else if (nrhs > 2) { 517 | mexErrMsgTxt("Too many input arguments."); 518 | } 519 | if (!mxIsChar(prhs[0])) { 520 | mexErrMsgTxt("Input must be a string."); 521 | } 522 | ReplacementStyle = JSON_REPLACEMENT_STYLE_UNDERSCORE; 523 | if (nrhs > 1) { 524 | if (!mxIsStruct(prhs[1])){ 525 | mexErrMsgTxt("Input must be a struct."); 526 | } 527 | nfields = mxGetNumberOfFields(prhs[1]); 528 | for (i = 0; i < nfields; i++) { 529 | field = mxGetFieldNameByNumber(prhs[1], i); 530 | if (!strcasecmp(field,"replacementStyle")) { 531 | mx = mxGetFieldByNumber(prhs[1],0,i); 532 | if (mx != NULL) { 533 | repsty = mxArrayToString(mx); 534 | if (!strcasecmp(repsty,"nop")) { 535 | ReplacementStyle = JSON_REPLACEMENT_STYLE_NOP; 536 | } 537 | else if (!strcasecmp(repsty,"underscore")) { 538 | ReplacementStyle = JSON_REPLACEMENT_STYLE_UNDERSCORE; 539 | } 540 | else if (!strcasecmp(repsty,"hex")) { 541 | ReplacementStyle = JSON_REPLACEMENT_STYLE_HEX; 542 | } 543 | else if (!strcasecmp(repsty,"delete")) { 544 | ReplacementStyle = JSON_REPLACEMENT_STYLE_DELETE; 545 | } 546 | else { 547 | mexErrMsgTxt("Unknown replacementStyle."); 548 | } 549 | mxFree(repsty); 550 | } 551 | } 552 | else { 553 | mexErrMsgTxt("Unknown optional parameter."); 554 | } 555 | } 556 | } 557 | 558 | /* Get JSON data as char array */ 559 | js = get_data(prhs[0], &jslen); 560 | 561 | /* Parse JSON data */ 562 | tok = parse(js, jslen); 563 | 564 | /* Create output structure */ 565 | create_struct(js, tok, &plhs[0]); 566 | 567 | mxFree(js); 568 | mxFree(tok); 569 | } 570 | -------------------------------------------------------------------------------- /jsonread.m: -------------------------------------------------------------------------------- 1 | function json = jsonread(filename, opts) 2 | % JSON (JavaScript Object Notation) parser - a compiled routine 3 | % FORMAT json = jsonread(filename, opts) 4 | % filename - name of a JSON file or JSON string 5 | % json - JSON structure 6 | % opts - structure of optional parameters: 7 | % replacementStyle: string to control how non-alphanumeric 8 | % characters are replaced {'underscore','hex','delete','nop'} 9 | % [Default: 'underscore'] 10 | % 11 | % References: 12 | % JSON Standard: http://www.json.org/ 13 | % JSMN C parser: http://zserge.com/jsmn.html 14 | % jsondecode: http://www.mathworks.com/help/matlab/ref/jsondecode.html 15 | 16 | % JSONio: https://www.gllmflndn.com/software/matlab/jsonio/ 17 | 18 | 19 | %-This is merely the help file for the compiled routine 20 | error('jsonread.c not compiled.'); 21 | 22 | % mex jsonread.c jsmn.c -DJSMN_PARENT_LINKS 23 | -------------------------------------------------------------------------------- /jsonread.mexa64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gllmflndn/JSONio/e6c5b3ea16142e8e428aa254fc042dfad6011c30/jsonread.mexa64 -------------------------------------------------------------------------------- /jsonread.mexmaca64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gllmflndn/JSONio/e6c5b3ea16142e8e428aa254fc042dfad6011c30/jsonread.mexmaca64 -------------------------------------------------------------------------------- /jsonread.mexmaci64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gllmflndn/JSONio/e6c5b3ea16142e8e428aa254fc042dfad6011c30/jsonread.mexmaci64 -------------------------------------------------------------------------------- /jsonread.mexw64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gllmflndn/JSONio/e6c5b3ea16142e8e428aa254fc042dfad6011c30/jsonread.mexw64 -------------------------------------------------------------------------------- /jsonwrite.m: -------------------------------------------------------------------------------- 1 | function varargout = jsonwrite(varargin) 2 | % Serialize a JSON (JavaScript Object Notation) structure 3 | % FORMAT jsonwrite(filename,json) 4 | % filename - JSON filename 5 | % json - JSON structure 6 | % 7 | % FORMAT S = jsonwrite(json) 8 | % json - JSON structure 9 | % S - serialized JSON structure (string) 10 | % 11 | % FORMAT [...] = jsonwrite(...,opts) 12 | % opts - structure or list of name/value pairs of optional parameters: 13 | % prettyPrint: indent output [Default: false] 14 | % replacementStyle: string to control how non-alphanumeric 15 | % characters are replaced {'underscore','hex','delete','nop'} 16 | % [Default: 'underscore'] 17 | % convertInfAndNaN: encode NaN, Inf and -Inf as "null" 18 | % [Default: true] 19 | % 20 | % References: 21 | % JSON Standard: https://www.json.org/ 22 | % jsonencode: https://www.mathworks.com/help/matlab/ref/jsonencode.html 23 | 24 | % JSONio: https://www.gllmflndn.com/software/matlab/jsonio/ 25 | 26 | 27 | %-Input parameters 28 | %-------------------------------------------------------------------------- 29 | opts = struct(... 30 | 'indent','',... 31 | 'prettyprint',false,... 32 | 'replacementstyle','underscore',... 33 | 'convertinfandnan',true); 34 | opt = {struct([])}; 35 | 36 | if ~nargin 37 | error('Not enough input arguments.'); 38 | elseif nargin == 1 39 | filename = ''; 40 | json = varargin{1}; 41 | else 42 | if ischar(varargin{1}) 43 | filename = varargin{1}; 44 | json = varargin{2}; 45 | opt = varargin(3:end); 46 | else 47 | filename = ''; 48 | json = varargin{1}; 49 | opt = varargin(2:end); 50 | end 51 | end 52 | if numel(opt) == 1 && isstruct(opt{1}) 53 | opt = opt{1}; 54 | elseif mod(numel(opt),2) == 0 55 | opt = cell2struct(opt(2:2:end),opt(1:2:end),2); 56 | else 57 | error('Invalid syntax.'); 58 | end 59 | fn = fieldnames(opt); 60 | for i=1:numel(fn) 61 | if ~isfield(opts,lower(fn{i})), warning('Unknown option "%s".',fn{i}); end 62 | opts.(lower(fn{i})) = opt.(fn{i}); 63 | end 64 | if opts.prettyprint 65 | opts.indent = ' '; 66 | end 67 | optregistry(opts); 68 | 69 | %-JSON serialization 70 | %-------------------------------------------------------------------------- 71 | fmt('init',sprintf(opts.indent)); 72 | S = jsonwrite_var(json,~isempty(opts.indent)); 73 | 74 | %-Output 75 | %-------------------------------------------------------------------------- 76 | if isempty(filename) 77 | varargout = { S }; 78 | else 79 | fid = fopen(filename,'wt'); 80 | if fid == -1 81 | error('Unable to open file "%s" for writing.',filename); 82 | end 83 | fprintf(fid,'%s',S); 84 | fclose(fid); 85 | end 86 | 87 | 88 | %========================================================================== 89 | function S = jsonwrite_var(json,tab) 90 | if nargin < 2, tab = ''; end 91 | if isstruct(json) || isa(json,'containers.Map') 92 | S = jsonwrite_struct(json,tab); 93 | elseif iscell(json) 94 | S = jsonwrite_cell(json,tab); 95 | elseif ischar(json) 96 | if size(json,1) <= 1 97 | S = jsonwrite_char(json); 98 | else 99 | S = jsonwrite_cell(cellstr(json),tab); 100 | end 101 | elseif isnumeric(json) || islogical(json) 102 | S = jsonwrite_numeric(json); 103 | elseif isa(json,'string') 104 | if numel(json) == 1 105 | if ismissing(json) 106 | S = 'null'; 107 | else 108 | S = jsonwrite_char(char(json)); 109 | end 110 | else 111 | json = arrayfun(@(x)x,json,'UniformOutput',false); 112 | json(cellfun(@(x) ismissing(x),json)) = {'null'}; 113 | idx = find(size(json)~=1); 114 | if numel(idx) == 1 % vector 115 | S = jsonwrite_cell(json,tab); 116 | else % array 117 | S = jsonwrite_cell(num2cell(json,setdiff(1:ndims(json),idx(1))),tab); 118 | end 119 | end 120 | elseif isa(json,'datetime') || isa(json,'categorical') 121 | S = jsonwrite_var(string(json)); 122 | elseif isa(json,'table') 123 | S = struct; 124 | s = size(json); 125 | vn = json.Properties.VariableNames; 126 | for i=1:s(1) 127 | for j=1:s(2) 128 | if iscell(json{i,j}) 129 | S(i).(vn{j}) = json{i,j}{1}; 130 | else 131 | S(i).(vn{j}) = json{i,j}; 132 | end 133 | end 134 | end 135 | S = jsonwrite_struct(S,tab); 136 | else 137 | if numel(json) ~= 1 138 | json = arrayfun(@(x)x,json,'UniformOutput',false); 139 | S = jsonwrite_cell(json,tab); 140 | else 141 | p = properties(json); 142 | if isempty(p), p = fieldnames(json); end % for pre-classdef 143 | s = struct; 144 | for i=1:numel(p) 145 | s.(p{i}) = json.(p{i}); 146 | end 147 | S = jsonwrite_struct(s,tab); 148 | %error('Class "%s" is not supported.',class(json)); 149 | end 150 | end 151 | 152 | %========================================================================== 153 | function S = jsonwrite_struct(json,tab) 154 | if numel(json) == 1 155 | if isstruct(json), fn = fieldnames(json); else fn = keys(json); end 156 | S = ['{' fmt('\n',tab)]; 157 | for i=1:numel(fn) 158 | key = fn{i}; 159 | if strcmp(optregistry('replacementStyle'),'hex') 160 | key = regexprep(key,... 161 | '^x0x([0-9a-fA-F]{2})', '${native2unicode(hex2dec($1))}'); 162 | key = regexprep(key,... 163 | '0x([0-9a-fA-F]{2})', '${native2unicode(hex2dec($1))}'); 164 | end 165 | if isstruct(json), val = json.(fn{i}); else val = json(fn{i}); end 166 | S = [S fmt(tab) jsonwrite_char(key) ':' fmt(' ',tab) ... 167 | jsonwrite_var(val,tab+1)]; 168 | if i ~= numel(fn), S = [S ',']; end 169 | S = [S fmt('\n',tab)]; 170 | end 171 | S = [S fmt(tab-1) '}']; 172 | else 173 | S = jsonwrite_cell(arrayfun(@(x) {x},json),tab); 174 | end 175 | 176 | %========================================================================== 177 | function S = jsonwrite_cell(json,tab) 178 | if numel(json) == 0 ... 179 | || (numel(json) == 1 && iscellstr(json)) ... 180 | || all(all(cellfun(@isnumeric,json))) ... 181 | || all(all(cellfun(@islogical,json))) 182 | tab = ''; 183 | end 184 | S = ['[' fmt('\n',tab)]; 185 | for i=1:numel(json) 186 | S = [S fmt(tab) jsonwrite_var(json{i},tab+1)]; 187 | if i ~= numel(json), S = [S ',']; end 188 | S = [S fmt('\n',tab)]; 189 | end 190 | S = [S fmt(tab-1) ']']; 191 | 192 | %========================================================================== 193 | function S = jsonwrite_char(json) 194 | % any-Unicode-character-except-"-or-\-or-control-character 195 | % \" \\ \/ \b \f \n \r \t \u four-hex-digits 196 | json = strrep(json,'\','\\'); 197 | json = strrep(json,'"','\"'); 198 | %json = strrep(json,'/','\/'); 199 | json = strrep(json,sprintf('\b'),'\b'); 200 | json = strrep(json,sprintf('\f'),'\f'); 201 | json = strrep(json,sprintf('\n'),'\n'); 202 | json = strrep(json,sprintf('\r'),'\r'); 203 | json = strrep(json,sprintf('\t'),'\t'); 204 | S = ['"' json '"']; 205 | 206 | %========================================================================== 207 | function S = jsonwrite_numeric(json) 208 | if any(imag(json(:))) 209 | error('Complex numbers not supported.'); 210 | end 211 | if numel(json) == 0 212 | S = jsonwrite_cell({}); 213 | return; 214 | elseif numel(json) > 1 215 | idx = find(size(json)~=1); 216 | if numel(idx) == 1 % vector 217 | if any(islogical(json)) || any(~isfinite(json)) 218 | S = jsonwrite_cell(num2cell(json),''); 219 | else 220 | S = ['[' sprintf('%23.16g,',json) ']']; % eq to num2str(json,16) 221 | S(end-1) = ''; % remove last "," 222 | S(S==' ') = []; 223 | end 224 | else % array 225 | S = jsonwrite_cell(num2cell(json,setdiff(1:ndims(json),idx(1))),''); 226 | end 227 | return; 228 | end 229 | if islogical(json) 230 | if json, S = 'true'; else S = 'false'; end 231 | elseif ~isfinite(json) 232 | if optregistry('convertinfandnan') 233 | S = 'null'; 234 | else 235 | if isnan(json) 236 | S = 'NaN'; 237 | elseif json > 0 238 | S = 'Infinity'; 239 | else 240 | S = '-Infinity'; 241 | end 242 | end 243 | else 244 | S = num2str(json,16); 245 | end 246 | 247 | %========================================================================== 248 | function b = fmt(varargin) 249 | persistent tab; 250 | if nargin == 2 && isequal(varargin{1},'init') 251 | tab = varargin{2}; 252 | end 253 | b = ''; 254 | if nargin == 1 255 | if varargin{1} > 0, b = repmat(tab,1,varargin{1}); end 256 | elseif nargin == 2 257 | if ~isempty(tab) && ~isempty(varargin{2}), b = sprintf(varargin{1}); end 258 | end 259 | 260 | %========================================================================== 261 | function val = optregistry(opts) 262 | persistent options 263 | if isstruct(opts) 264 | options = opts; 265 | else 266 | val = options.(lower(opts)); 267 | end 268 | -------------------------------------------------------------------------------- /tests/test_jsonread.m: -------------------------------------------------------------------------------- 1 | function tests = test_jsonread 2 | % Unit Tests for jsonread 3 | 4 | % JSONio: https://www.gllmflndn.com/software/matlab/jsonio/ 5 | 6 | tests = functiontests(localfunctions); 7 | 8 | 9 | function test_jsonread_from_string_1(testCase) 10 | json = '["one", "two", "three"]'; 11 | 12 | exp = {'one';'two';'three'}; 13 | act = jsonread(json); 14 | testCase.verifyTrue(isequal(exp, act)); 15 | 16 | function test_jsonread_from_string_2(testCase) 17 | json = '{"Width":800,"Height":600,"Title":"View from the 15th Floor","Animated":false,"IDs":[116,943,234,38793]}'; 18 | 19 | exp = struct('Width',800,'Height',600,'Title','View from the 15th Floor','Animated',false,'IDs',[116;943;234;38793]); 20 | act = jsonread(json); 21 | testCase.verifyTrue(isequal(exp, act)); 22 | 23 | function test_jsonread_all_types(testCase) 24 | 25 | % JSON Data Type | MATLAB Data Type 26 | 27 | % null, in numeric arrays | NaN 28 | json = '[1, 2, null, 4]'; 29 | exp = [1; 2; NaN; 4]; 30 | act = jsonread(json); 31 | %testCase.verifyTrue(isequaln(exp, act)); 32 | 33 | % null, in nonnumeric arrays| empty double [] 34 | json = '{"null": null}'; 35 | exp = struct('null',[]); 36 | act = jsonread(json); 37 | testCase.verifyTrue(isequal(exp, act)); 38 | 39 | % Boolean | scalar logical 40 | json = '{"logical": false}'; 41 | exp = struct('logical',false); 42 | act = jsonread(json); 43 | testCase.verifyTrue(isequal(exp, act)); 44 | 45 | json = '{"logical": true}'; 46 | exp = struct('logical',true); 47 | act = jsonread(json); 48 | testCase.verifyTrue(isequal(exp, act)); 49 | 50 | % Number | scalar double 51 | json = '{"number": 3.14}'; 52 | exp = struct('number',3.14); 53 | act = jsonread(json); 54 | testCase.verifyTrue(isequal(exp, act)); 55 | 56 | % String | character vector 57 | json = '{"string": "string"}'; 58 | exp = struct('string','string'); 59 | act = jsonread(json); 60 | testCase.verifyTrue(isequal(exp, act)); 61 | 62 | % Object (In JSON, object | scalar structure 63 | % means an unordered set | (Names are made 64 | % of name-value pairs.) | valid.) 65 | json = '{"object": {"field1": 1, "field-2": 2, "3field": 3}}'; 66 | exp = struct('object',struct('field1',1,'field_2',2,'x3field',3)); 67 | act = jsonread(json); 68 | testCase.verifyTrue(isequal(exp, act)); 69 | 70 | json = '{"object": {"field 1": 1, "field two": 2, " field Three ": 3}}'; 71 | exp = struct('object',struct('field1',1,'fieldTwo',2,'fieldThree',3)); 72 | act = jsonread(json); 73 | testCase.verifyTrue(isequal(exp, act)); 74 | 75 | json = '{"object": {"": 1}}'; 76 | exp = struct('object',struct('x',1)); 77 | act = jsonread(json); 78 | testCase.verifyTrue(isequal(exp, act)); 79 | 80 | % Array, when elements are | cell array 81 | % of different data types | 82 | json = '{"array": ["a", 1]}'; 83 | exp = struct('array',{{'a';1}}); 84 | act = jsonread(json); 85 | testCase.verifyTrue(isequal(exp, act)); 86 | 87 | % Array of booleans | logical array 88 | json = '{"logical_array": [true, false]}'; 89 | exp = struct('logical_array',[true;false]); 90 | act = jsonread(json); 91 | testCase.verifyTrue(isequal(exp, act)); 92 | 93 | % Array of numbers | double array 94 | json = '{"number_array": [1, 2, 3, 5, 8, 13]}'; 95 | exp = struct('number_array',[1; 2; 3; 5; 8; 13]); 96 | act = jsonread(json); 97 | testCase.verifyTrue(isequal(exp, act)); 98 | 99 | % Array of strings | cellstr 100 | json = '{"cellstr": ["Statistical","Parametric","Mapping"]}'; 101 | exp = struct('cellstr',{{'Statistical';'Parametric';'Mapping'}}); 102 | act = jsonread(json); 103 | testCase.verifyTrue(isequal(exp, act)); 104 | 105 | % Array of objects, when | structure array 106 | % all objects have the | 107 | % same set of names | 108 | json = '{"structarray": [{"a":1,"b":2},{"a":3,"b":4}]}'; 109 | exp = struct('structarray',struct('a',{1;3},'b',{2;4})); 110 | act = jsonread(json); 111 | testCase.verifyTrue(isequal(exp, act)); 112 | 113 | % Array of objects, when | cell array of 114 | % objects have different | scalar structures 115 | % names | 116 | json = '{"cellarray": [{"a":1,"b":2},{"a":3,"c":4}]}'; 117 | exp = struct('cellarray',{{struct('a',1,'b',2);struct('a',3,'c',4)}}); 118 | act = jsonread(json); 119 | testCase.verifyTrue(isequal(exp, act)); 120 | 121 | % Array of objects, when | cell array of 122 | % all objects have the |scalar structures 123 | % same set of names | 124 | % but different order 125 | json = '{"structarray": [{"a":1,"b":2},{"b":3,"a":4}]}'; 126 | exp = struct('structarray',{{struct('a',1,'b',2);struct('b',3,'a',4)}}); 127 | act = jsonread(json); 128 | testCase.verifyTrue(isequal(exp, act)); 129 | 130 | % empty struct 131 | json = '{"a":"aa","b":{},"c":"cc"}'; 132 | exp = struct('a','aa','b',struct(),'c','cc'); 133 | act = jsonread(json); 134 | testCase.verifyTrue(isequal(exp, act)); 135 | 136 | % empty array 137 | json = '{"a":"aa","b":[],"c":"cc"}'; 138 | exp = struct('a','aa','b',[],'c','cc'); 139 | act = jsonread(json); 140 | testCase.verifyTrue(isequal(exp, act)); 141 | 142 | % NaN and Inf 143 | json = '[1,NaN,Inf]'; 144 | exp = [1;NaN;Inf]; 145 | act = jsonread(json); 146 | testCase.verifyTrue(isequaln(exp, act)); 147 | 148 | % numbers and booleans 149 | json = '[1,NaN,Inf,true]'; 150 | exp = {1;NaN;Inf;true}; 151 | act = jsonread(json); 152 | testCase.verifyTrue(isequaln(exp, act)); 153 | 154 | % numbers and arrays of numbers 155 | json = '[1,2,[3,4]]'; 156 | exp = {1;2;[3;4]}; 157 | act = jsonread(json); 158 | testCase.verifyTrue(isequal(exp, act)); 159 | 160 | json = '[[1,2]]'; 161 | exp = [1 2]; 162 | act = jsonread(json); 163 | testCase.verifyTrue(isequal(exp, act)); 164 | 165 | json = '[[1,2],[3,4]]'; 166 | exp = [1 2;3 4]; 167 | act = jsonread(json); 168 | testCase.verifyTrue(isequal(exp, act)); 169 | 170 | json = '[[1,2],[3,4,5]]'; 171 | exp = {[1;2];[3;4;5]}; 172 | act = jsonread(json); 173 | testCase.verifyTrue(isequal(exp, act)); 174 | 175 | json = '[[[1,2],[3,4]],[[5,6],[7,8]]]'; 176 | exp = cat(3,[1,3;5,7],[2,4;6,8]); 177 | act = jsonread(json); 178 | testCase.verifyTrue(isequal(exp, act)); 179 | -------------------------------------------------------------------------------- /tests/test_jsonwrite.m: -------------------------------------------------------------------------------- 1 | function tests = test_jsonwrite 2 | % Unit Tests for jsonwrite 3 | 4 | % JSONio: https://www.gllmflndn.com/software/matlab/jsonio/ 5 | 6 | tests = functiontests(localfunctions); 7 | 8 | 9 | function test_jsonwrite_array(testCase) 10 | exp = {'one';'two';'three'}; 11 | act = jsonread(jsonwrite(exp)); 12 | testCase.verifyTrue(isequal(exp, act)); 13 | 14 | exp = 2; 15 | act = nnz(jsonwrite(1:3) == ','); 16 | testCase.verifyTrue(isequal(exp, act)); 17 | 18 | function test_jsonwrite_object(testCase) 19 | exp = struct('Width',800,'Height',600,'Title','View from the 15th Floor','Animated',false,'IDs',[116;943;234;38793]); 20 | act = jsonread(jsonwrite(exp)); 21 | testCase.verifyTrue(isequal(exp, act)); 22 | 23 | function test_jsonwrite_all_types(testCase) 24 | exp = []; 25 | act = jsonread(jsonwrite(exp)); 26 | testCase.verifyTrue(isequal(exp, act)); 27 | 28 | exp = [true;false]; 29 | act = jsonread(jsonwrite(exp)); 30 | testCase.verifyTrue(isequal(exp, act)); 31 | 32 | exp = struct('a',''); 33 | act = jsonread(jsonwrite(exp)); 34 | testCase.verifyTrue(isequal(exp, act)); 35 | 36 | str = struct('str',reshape(1:9,3,3)); 37 | exp = jsonread('{"str":[[1,4,7],[2,5,8],[3,6,9]]}'); 38 | act = jsonread(jsonwrite(str)); 39 | testCase.verifyTrue(isequal(act, exp)); 40 | 41 | str = [1,2,NaN,3,Inf]; 42 | exp = jsonread('[1,2,null,3,null]'); 43 | act = jsonread(jsonwrite(str)); 44 | testCase.verifyTrue(isequaln(act, exp)); 45 | 46 | %function test_jsonwrite_chararray(testCase) 47 | %str = char('one','two','three'); 48 | %exp = {'one ';'two ';'three'}; 49 | %act = jsonread(jsonwrite(str)); 50 | %testCase.verifyTrue(isequal(exp, act)); 51 | 52 | function test_options(testCase) 53 | exp = struct('Width',800,'Height',NaN,'Title','View','Bool',true); 54 | jsonwrite(exp,'indent',''); 55 | jsonwrite(exp,'indent',' '); 56 | jsonwrite(exp,'replacementStyle','underscore'); 57 | jsonwrite(exp,'replacementStyle','hex'); 58 | jsonwrite(exp,'convertInfAndNaN',true); 59 | jsonwrite(exp,'convertInfAndNaN',false); 60 | jsonwrite(exp,'indent',' ','replacementStyle','hex','convertInfAndNaN',false); 61 | jsonwrite(exp,struct('indent','\t')); 62 | jsonwrite(exp,struct('indent','\t','convertInfAndNaN',false)); 63 | --------------------------------------------------------------------------------