GNU Octave 10.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
jsondecode.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2020-2025 The Octave Project Developers
4//
5// See the file COPYRIGHT.md in the top-level directory of this
6// distribution or <https://octave.org/copyright/>.
7//
8// This file is part of Octave.
9//
10// Octave is free software: you can redistribute it and/or modify it
11// under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// Octave is distributed in the hope that it will be useful, but
16// WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with Octave; see the file COPYING. If not, see
22// <https://www.gnu.org/licenses/>.
23//
24////////////////////////////////////////////////////////////////////////
25
26#if defined (HAVE_CONFIG_H)
27# include "config.h"
28#endif
29
30#include "defun.h"
31#include "error.h"
32#include "errwarn.h"
33#include "oct-string.h"
34#include "ovl.h"
35#include "utils.h"
36
37#if defined (HAVE_RAPIDJSON)
38# include <rapidjson/document.h>
39# include <rapidjson/error/en.h>
40#endif
41
43
44#if defined (HAVE_RAPIDJSON)
45
46static octave_value
47decode (const rapidjson::Value& val,
48 const octave::make_valid_name_options *options);
49
50//! Decodes a numerical JSON value into a scalar number.
51//!
52//! @param val JSON value that is guaranteed to be a numerical value.
53//!
54//! @return @ref octave_value that contains the numerical value of @p val.
55//!
56//! @b Example:
57//!
58//! @code{.cc}
59//! rapidjson::Document d;
60//! d.Parse ("123");
61//! octave_value num = decode_number (d);
62//! @endcode
63
64static octave_value
65decode_number (const rapidjson::Value& val)
66{
67 if (val.IsUint ())
68 return octave_value (val.GetUint ());
69 else if (val.IsInt ())
70 return octave_value (val.GetInt ());
71 else if (val.IsUint64 ())
72 return octave_value (val.GetUint64 ());
73 else if (val.IsInt64 ())
74 return octave_value (val.GetInt64 ());
75 else if (val.IsDouble ())
76 return octave_value (val.GetDouble ());
77 else
78 error ("jsondecode: unidentified type");
79}
80
81//! Decodes a JSON object into a scalar struct.
82//!
83//! @param val JSON value that is guaranteed to be a JSON object.
84//! @param options @c ReplacementStyle and @c Prefix options with their values.
85//!
86//! @return @ref octave_value that contains the equivalent scalar struct of @p val.
87//!
88//! @b Example:
89//!
90//! @code{.cc}
91//! rapidjson::Document d;
92//! d.Parse ("{\"a\": 1, \"b\": 2}");
93//! octave_value struct = decode_object (d, octave_value_list ());
94//! @endcode
95
96static octave_value
97decode_object (const rapidjson::Value& val,
98 const octave::make_valid_name_options *options)
99{
100 octave_scalar_map retval;
101
102 for (const auto& pair : val.GetObject ())
103 {
104 // Validator function "matlab.lang.makeValidName" to guarantee legitimate
105 // variable name.
106 std::string varname = pair.name.GetString ();
107 if (options != nullptr)
108 octave::make_valid_name (varname, *options);
109 retval.assign (varname, decode (pair.value, options));
110 }
111
112 return retval;
113}
114
115//! Decodes a JSON array that contains only numerical or null values
116//! into an NDArray.
117//!
118//! @param val JSON value that is guaranteed to be a numeric array.
119//!
120//! @return @ref octave_value that contains the equivalent NDArray of @p val.
121//!
122//! @b Example:
123//!
124//! @code{.cc}
125//! rapidjson::Document d;
126//! d.Parse ("[1, 2, 3, 4]");
127//! octave_value numeric_array = decode_numeric_array (d);
128//! @endcode
129
130static octave_value
131decode_numeric_array (const rapidjson::Value& val)
132{
133 NDArray retval (dim_vector (val.Size (), 1));
134 octave_idx_type index = 0;
135 for (const auto& elem : val.GetArray ())
136 retval(index++) = elem.IsNull () ? octave_NaN
137 : decode_number (elem).double_value ();
138 return retval;
139}
140
141//! Decodes a JSON array that contains only boolean values into a boolNDArray.
142//!
143//! @param val JSON value that is guaranteed to be a boolean array.
144//!
145//! @return @ref octave_value that contains the equivalent boolNDArray of @p val.
146//!
147//! @b Example:
148//!
149//! @code{.cc}
150//! rapidjson::Document d;
151//! d.Parse ("[true, false, true]");
152//! octave_value boolean_array = decode_boolean_array (d);
153//! @endcode
154
155static octave_value
156decode_boolean_array (const rapidjson::Value& val)
157{
158 boolNDArray retval (dim_vector (val.Size (), 1));
159 octave_idx_type index = 0;
160 for (const auto& elem : val.GetArray ())
161 retval(index++) = elem.GetBool ();
162 return retval;
163}
164
165//! Decodes a JSON array that contains different types
166//! or string values only into a Cell.
167//!
168//! @param val JSON value that is guaranteed to be a mixed or string array.
169//! @param options @c ReplacementStyle and @c Prefix options with their values.
170//!
171//! @return @ref octave_value that contains the equivalent Cell of @p val.
172//!
173//! @b Example (decoding a string array):
174//!
175//! @code{.cc}
176//! rapidjson::Document d;
177//! d.Parse ("[\"foo\", \"bar\", \"baz\"]");
178//! octave_value cell = decode_string_and_mixed_array (d, octave_value_list ());
179//! @endcode
180//!
181//! @b Example (decoding a mixed array):
182//!
183//! @code{.cc}
184//! rapidjson::Document d;
185//! d.Parse ("[\"foo\", 123, true]");
186//! octave_value cell = decode_string_and_mixed_array (d, octave_value_list ());
187//! @endcode
188
189static octave_value
190decode_string_and_mixed_array (const rapidjson::Value& val,
191 const octave::make_valid_name_options *options)
192{
193 Cell retval (dim_vector (val.Size (), 1));
194 octave_idx_type index = 0;
195 for (const auto& elem : val.GetArray ())
196 retval(index++) = decode (elem, options);
197 return retval;
198}
199
200//! Decodes a JSON array that contains only objects into a Cell or struct array
201//! depending on the similarity of the objects' keys.
202//!
203//! @param val JSON value that is guaranteed to be an object array.
204//! @param options @c ReplacementStyle and @c Prefix options with their values.
205//!
206//! @return @ref octave_value that contains the equivalent Cell
207//! or struct array of @p val.
208//!
209//! @b Example (returns a struct array):
210//!
211//! @code{.cc}
212//! rapidjson::Document d;
213//! d.Parse ("[{\"a\":1,\"b\":2},{\"a\":3,\"b\":4}]");
214//! octave_value object_array = decode_object_array (d, octave_value_list ());
215//! @endcode
216//!
217//! @b Example (returns a Cell):
218//!
219//! @code{.cc}
220//! rapidjson::Document d;
221//! d.Parse ("[{\"a\":1,\"b\":2},{\"b\":3,\"a\":4}]");
222//! octave_value object_array = decode_object_array (d, octave_value_list ());
223//! @endcode
224
225static octave_value
226decode_object_array (const rapidjson::Value& val,
227 const octave::make_valid_name_options *options)
228{
229 Cell struct_cell = decode_string_and_mixed_array (val, options).cell_value ();
230 string_vector field_names = struct_cell(0).scalar_map_value ().fieldnames ();
231
232 bool same_field_names = true;
233 for (octave_idx_type i = 1; i < struct_cell.numel (); ++i)
234 if (field_names.std_list ()
235 != struct_cell(i).scalar_map_value ().fieldnames ().std_list ())
236 {
237 same_field_names = false;
238 break;
239 }
240
241 if (same_field_names)
242 {
243 octave_map struct_array;
244 const dim_vector& struct_array_dims = dim_vector (struct_cell.numel (), 1);
245
246 if (field_names.numel ())
247 {
248 Cell value (struct_array_dims);
249 for (octave_idx_type i = 0; i < field_names.numel (); ++i)
250 {
251 for (octave_idx_type k = 0; k < struct_cell.numel (); ++k)
252 value(k) = struct_cell(k).scalar_map_value ()
253 .getfield (field_names(i));
254 struct_array.assign (field_names(i), value);
255 }
256 }
257 else
258 struct_array.resize (struct_array_dims, true);
259
260 return struct_array;
261 }
262 else
263 return struct_cell;
264}
265
266//! Decodes a JSON array that contains only arrays into a Cell or an NDArray
267//! depending on the dimensions and element types of the sub-arrays.
268//!
269//! @param val JSON value that is guaranteed to be an array of arrays.
270//! @param options @c ReplacementStyle and @c Prefix options with their values.
271//!
272//! @return @ref octave_value that contains the equivalent Cell
273//! or NDArray of @p val.
274//!
275//! @b Example (returns an NDArray):
276//!
277//! @code{.cc}
278//! rapidjson::Document d;
279//! d.Parse ("[[1, 2], [3, 4]]");
280//! octave_value array = decode_array_of_arrays (d, octave_value_list ());
281//! @endcode
282//!
283//! @b Example (returns a Cell):
284//!
285//! @code{.cc}
286//! rapidjson::Document d;
287//! d.Parse ("[[1, 2], [3, 4, 5]]");
288//! octave_value cell = decode_array_of_arrays (d, octave_value_list ());
289//! @endcode
290
291static octave_value
292decode_array_of_arrays (const rapidjson::Value& val,
293 const octave::make_valid_name_options *options)
294{
295 // Some arrays should be decoded as NDArrays and others as cell arrays
296 Cell cell = decode_string_and_mixed_array (val, options).cell_value ();
297
298 // Only arrays with sub-arrays of booleans and numericals will return NDArray
299 bool is_bool = cell(0).is_bool_matrix ();
300 bool is_struct = cell(0).isstruct ();
301 string_vector field_names = is_struct ? cell(0).map_value ().fieldnames ()
302 : string_vector ();
303 const dim_vector& sub_array_dims = cell(0).dims ();
304 octave_idx_type sub_array_ndims = cell(0).ndims ();
305 octave_idx_type cell_numel = cell.numel ();
306 for (octave_idx_type i = 0; i < cell_numel; ++i)
307 {
308 // If one element is cell return the cell array as at least one of the
309 // sub-arrays area either an array of: strings, objects or mixed array
310 if (cell(i).iscell ())
311 return cell;
312 // If not the same dim of elements or dim = 0, return cell array
313 if (cell(i).dims () != sub_array_dims || sub_array_dims == dim_vector ())
314 return cell;
315 // If not numeric sub-arrays only or bool sub-arrays only,
316 // return cell array
317 if (cell(i).is_bool_matrix () != is_bool)
318 return cell;
319 // If not struct arrays only, return cell array
320 if (cell(i).isstruct () != is_struct)
321 return cell;
322 // If struct arrays have different fields, return cell array
323 if (is_struct && (field_names.std_list ()
324 != cell(i).map_value ().fieldnames ().std_list ()))
325 return cell;
326 }
327
328 // Calculate the dims of the output array
329 dim_vector array_dims;
330 array_dims.resize (sub_array_ndims + 1);
331 array_dims(0) = cell_numel;
332 for (auto i = 1; i < sub_array_ndims + 1; i++)
333 array_dims(i) = sub_array_dims(i-1);
334
335 if (is_struct)
336 {
337 octave_map struct_array;
338 array_dims.chop_trailing_singletons ();
339
340 if (field_names.numel ())
341 {
342 Cell value (array_dims);
343 octave_idx_type sub_array_numel = sub_array_dims.numel ();
344
345 for (octave_idx_type j = 0; j < field_names.numel (); ++j)
346 {
347 // Populate the array with specific order to generate
348 // MATLAB-identical output.
349 for (octave_idx_type k = 0; k < cell_numel; ++k)
350 {
351 Cell sub_array_value = cell(k).map_value ()
352 .getfield (field_names(j));
353 for (octave_idx_type i = 0; i < sub_array_numel; ++i)
354 value(k + i * cell_numel) = sub_array_value(i);
355 }
356 struct_array.assign (field_names(j), value);
357 }
358 }
359 else
360 struct_array.resize(array_dims, true);
361
362 return struct_array;
363 }
364 else
365 {
366 NDArray array (array_dims);
367
368 // Populate the array with specific order to generate MATLAB-identical
369 // output.
370 octave_idx_type sub_array_numel = array.numel () / cell_numel;
371 for (octave_idx_type k = 0; k < cell_numel; ++k)
372 {
373 NDArray sub_array_value = cell(k).array_value ();
374 for (octave_idx_type i = 0; i < sub_array_numel; ++i)
375 array(k + i * cell_numel) = sub_array_value(i);
376 }
377
378 if (is_bool)
379 return boolNDArray (array);
380 else
381 return array;
382 }
383}
384
385//! Decodes any type of JSON arrays. This function only serves as an interface
386//! by choosing which function to call from the previous functions.
387//!
388//! @param val JSON value that is guaranteed to be an array.
389//! @param options @c ReplacementStyle and @c Prefix options with their values.
390//!
391//! @return @ref octave_value that contains the output of decoding @p val.
392//!
393//! @b Example:
394//!
395//! @code{.cc}
396//! rapidjson::Document d;
397//! d.Parse ("[[1, 2], [3, 4, 5]]");
398//! octave_value array = decode_array (d, octave_value_list ());
399//! @endcode
400
401static octave_value
402decode_array (const rapidjson::Value& val,
403 const octave::make_valid_name_options *options)
404{
405 // Handle empty arrays
406 if (val.Empty ())
407 return NDArray ();
408
409 // Compare with other elements to know if the array has multiple types
410 rapidjson::Type array_type = val[0].GetType ();
411 // Check if the array is numeric and if it has multiple types
412 bool same_type = true;
413 bool is_numeric = true;
414 for (const auto& elem : val.GetArray ())
415 {
416 rapidjson::Type current_elem_type = elem.GetType ();
417 if (is_numeric && ! (current_elem_type == rapidjson::kNullType
418 || current_elem_type == rapidjson::kNumberType))
419 is_numeric = false;
420 if (same_type && (current_elem_type != array_type))
421 // RapidJSON doesn't have kBoolean Type it has kTrueType and kFalseType
422 if (! ((current_elem_type == rapidjson::kTrueType
423 && array_type == rapidjson::kFalseType)
424 || (current_elem_type == rapidjson::kFalseType
425 && array_type == rapidjson::kTrueType)))
426 same_type = false;
427 }
428
429 if (is_numeric)
430 return decode_numeric_array (val);
431
432 if (same_type && (array_type != rapidjson::kStringType))
433 {
434 if (array_type == rapidjson::kTrueType
435 || array_type == rapidjson::kFalseType)
436 return decode_boolean_array (val);
437 else if (array_type == rapidjson::kObjectType)
438 return decode_object_array (val, options);
439 else if (array_type == rapidjson::kArrayType)
440 return decode_array_of_arrays (val, options);
441 else
442 error ("jsondecode: unidentified type");
443 }
444 else
445 return decode_string_and_mixed_array (val, options);
446}
447
448//! Decodes any JSON value. This function only serves as an interface
449//! by choosing which function to call from the previous functions.
450//!
451//! @param val JSON value.
452//! @param options @c ReplacementStyle and @c Prefix options with their values.
453//!
454//! @return @ref octave_value that contains the output of decoding @p val.
455//!
456//! @b Example:
457//!
458//! @code{.cc}
459//! rapidjson::Document d;
460//! d.Parse ("[{\"a\":1,\"b\":2},{\"b\":3,\"a\":4}]");
461//! octave_value value = decode (d, octave_value_list ());
462//! @endcode
463
464static octave_value
465decode (const rapidjson::Value& val,
466 const octave::make_valid_name_options *options)
467{
468 if (val.IsBool ())
469 return val.GetBool ();
470 else if (val.IsNumber ())
471 return decode_number (val);
472 else if (val.IsString ())
473 return val.GetString ();
474 else if (val.IsObject ())
475 return decode_object (val, options);
476 else if (val.IsNull ())
477 return NDArray ();
478 else if (val.IsArray ())
479 return decode_array (val, options);
480 else
481 error ("jsondecode: unidentified type");
482}
483
484#endif
485
486DEFUN (jsondecode, args, ,
487 doc: /* -*- texinfo -*-
488@deftypefn {} {@var{object} =} jsondecode (@var{JSON_txt})
489@deftypefnx {} {@var{object} =} jsondecode (@dots{}, "ReplacementStyle", @var{rs})
490@deftypefnx {} {@var{object} =} jsondecode (@dots{}, "Prefix", @var{pfx})
491@deftypefnx {} {@var{object} =} jsondecode (@dots{}, "makeValidName", @var{TF})
492
493Decode text that is formatted in JSON.
494
495The input @var{JSON_txt} is a string that contains JSON text.
496
497The output @var{object} is an Octave object that contains the result of
498decoding @var{JSON_txt}.
499
500For more information about the options @qcode{"ReplacementStyle"} and
501@qcode{"Prefix"},
502@pxref{XREFmatlab_lang_makeValidName,,@code{matlab.lang.makeValidName}}.
503
504If the value of the option @qcode{"makeValidName"} is false then names
505will not be changed by @code{matlab.lang.makeValidName} and the
506@qcode{"ReplacementStyle"} and @qcode{"Prefix"} options will be ignored.
507
508NOTE: Decoding and encoding JSON text is not guaranteed to reproduce the
509original text as some names may be changed by @code{matlab.lang.makeValidName}.
510
511This table shows the conversions from JSON data types to Octave data types:
512
513@multitable @columnfractions 0.50 0.50
514@headitem JSON data type @tab Octave data type
515@item Boolean @tab scalar logical
516@item Number @tab scalar double
517@item String @tab vector of characters
518@item Object @tab scalar struct (field names of the struct may be different from the keys of the JSON object due to @code{matlab_lang_makeValidName}
519@item null, inside a numeric array @tab @code{NaN}
520@item null, inside a non-numeric array @tab empty double array @code{[]}
521@item Array, of different data types @tab cell array
522@item Array, of Booleans @tab logical array
523@item Array, of Numbers @tab double array
524@item Array, of Strings @tab cell array of character vectors (@code{cellstr})
525@item Array of Objects, same field names @tab struct array
526@item Array of Objects, different field names @tab cell array of scalar structs
527@end multitable
528
529Examples:
530
531@example
532@group
533jsondecode ('[1, 2, null, 3]')
534 @result{} ans =
535
536 1
537 2
538 NaN
539 3
540@end group
541
542@group
543jsondecode ('["foo", "bar", ["foo", "bar"]]')
544 @result{} ans =
545 @{
546 [1,1] = foo
547 [2,1] = bar
548 [3,1] =
549 @{
550 [1,1] = foo
551 [2,1] = bar
552 @}
553
554 @}
555@end group
556
557@group
558jsondecode ('@{"nu#m#ber": 7, "s#tr#ing": "hi"@}', ...
559 'ReplacementStyle', 'delete')
560 @result{} scalar structure containing the fields:
561
562 number = 7
563 string = hi
564@end group
565
566@group
567jsondecode ('@{"nu#m#ber": 7, "s#tr#ing": "hi"@}', ...
568 'makeValidName', false)
569 @result{} scalar structure containing the fields:
570
571 nu#m#ber = 7
572 s#tr#ing = hi
573@end group
574
575@group
576jsondecode ('@{"1": "one", "2": "two"@}', 'Prefix', 'm_')
577 @result{} scalar structure containing the fields:
578
579 m_1 = one
580 m_2 = two
581@end group
582@end example
583
584@seealso{jsonencode, matlab.lang.makeValidName}
585@end deftypefn */)
586{
587#if defined (HAVE_RAPIDJSON)
588
589 int nargin = args.length ();
590
591 // makeValidName options are pairs, the number of arguments must be odd.
592 if (! (nargin % 2))
593 print_usage ();
594
595 // Detect if the user wants to use makeValidName
596 bool use_makeValidName = true;
597 octave_value_list make_valid_name_params;
598 for (auto i = 1; i < nargin; i = i + 2)
599 {
600 std::string parameter = args(i).xstring_value ("jsondecode: "
601 "option argument must be a string");
602 if (string::strcmpi (parameter, "makeValidName"))
603 {
604 use_makeValidName = args(i + 1).strict_bool_value ("jsondecode: "
605 "'makeValidName' value must be a bool");
606 }
607 else
608 make_valid_name_params.append (args.slice(i, 2));
609 }
610
612 = use_makeValidName ? new make_valid_name_options (make_valid_name_params)
613 : nullptr;
614
615 unwind_action del_opts ([options] () { if (options) delete options; });
616
617 if (! args(0).is_string ())
618 error ("jsondecode: JSON_TXT must be a character string");
619
620 std::string json = args(0).string_value ();
621 rapidjson::Document d;
622 // DOM is chosen instead of SAX as SAX publishes events to a handler that
623 // decides what to do depending on the event only. This will cause a
624 // problem in decoding JSON arrays as the output may be an array or a cell
625 // and that doesn't only depend on the event (startArray) but also on the
626 // types of the elements inside the array.
627 d.Parse <rapidjson::kParseNanAndInfFlag> (json.c_str ());
628
629 if (d.HasParseError ())
630 error ("jsondecode: parse error at offset %u: %s\n",
631 static_cast<unsigned int> (d.GetErrorOffset ()) + 1,
632 rapidjson::GetParseError_En (d.GetParseError ()));
633
634 return decode (d, options);
635
636#else
637
638 octave_unused_parameter (args);
639
640 err_disabled_feature ("jsondecode", "JSON decoding through RapidJSON");
641
642#endif
643}
644
645/*
646Functional BIST tests are located in test/json/jsondecode_BIST.tst
647
648## Input validation tests
649%!testif HAVE_RAPIDJSON
650%! fail ("jsondecode ()");
651%! fail ("jsondecode ('1', 2)");
652%! fail ("jsondecode (1)", "JSON_TXT must be a character string");
653%! fail ("jsondecode ('12-')", "parse error at offset 3");
654
655*/
656
657OCTAVE_END_NAMESPACE(octave)
const dim_vector & dims() const
Return a const-reference so that dims ()(i) works efficiently.
Definition Array.h:507
int ndims() const
Size of the specified dimension.
Definition Array.h:679
octave_idx_type numel() const
Number of elements in the array.
Definition Array.h:418
Definition Cell.h:41
Vector representing the dimensions (size) of an Array.
Definition dim-vector.h:90
octave_idx_type numel(int n=0) const
Number of elements that a matrix with this dimensions would have.
Definition dim-vector.h:331
void chop_trailing_singletons()
Definition dim-vector.h:160
void resize(int n, int fill_value=0)
Definition dim-vector.h:268
Helper class for make_valid_name function calls.
Definition utils.h:55
void resize(const dim_vector &dv, bool fill=false)
Definition oct-map.cc:574
void assign(const std::string &k, const Cell &val)
Definition oct-map.h:344
void assign(const std::string &k, const octave_value &val)
Definition oct-map.h:230
octave_value_list & append(const octave_value &val)
Definition ovl.cc:98
octave_idx_type numel() const
Definition str-vec.h:98
std::list< std::string > std_list() const
Definition str-vec.cc:172
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage()
Definition defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition defun.h:56
void error(const char *fmt,...)
Definition error.cc:1003
void err_disabled_feature(const std::string &fcn, const std::string &feature, const std::string &pkg)
Definition errwarn.cc:53
#define octave_NaN
Definition lo-ieee.h:46
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d