|
173 | 173 |
|
174 | 174 | % constructor: initialize a jdict object |
175 | 175 | function obj = jdict(val, varargin) |
176 | | - obj.flags__ = struct('builtinjson', 0); |
| 176 | + obj.flags__ = getflags_(); |
177 | 177 | obj.attr = containers.Map(); |
178 | 178 | obj.schema = []; |
179 | 179 | obj.currentpath__ = char(36); |
180 | 180 | obj.root__ = obj; |
181 | 181 | if (nargin >= 1) |
182 | 182 | if (~isempty(varargin)) |
183 | 183 | allflags = [varargin(1:2:end); varargin(2:2:end)]; |
184 | | - obj.flags__ = struct(allflags{:}); |
| 184 | + obj.flags__ = mergestruct_(obj.flags__, struct(allflags{:})); |
185 | 185 | if (isfield(obj.flags__, 'attr')) |
186 | 186 | obj.attr = obj.flags__.attr; |
187 | 187 | end |
|
211 | 211 |
|
212 | 212 | % overloaded numel to prevent subsref from outputting many outputs |
213 | 213 | function n = numel(obj, varargin) |
214 | | - if (exist('OCTAVE_VERSION', 'builtin') ~= 0) |
| 214 | + if (obj.flags__.isoctave_) |
215 | 215 | n = 1; |
216 | 216 | else |
217 | 217 | n = max(1, (nargin > 1) + numel(obj.data) * (nargin == 1)); |
|
276 | 276 | tempobj.attr = obj.attr; |
277 | 277 | tempobj.schema = obj.schema; |
278 | 278 | tempobj.currentpath__ = trackpath; |
279 | | - if (exist('OCTAVE_VERSION', 'builtin') ~= 0 && regexp(OCTAVE_VERSION, '^5\.')) |
| 279 | + if (obj.flags__.isoctave_ && regexp(OCTAVE_VERSION, '^5\.')) |
280 | 280 | val = membercall_(tempobj, idx.subs, idxkey(i + 1).subs{:}); |
281 | 281 | else |
282 | 282 | fhandle = str2func(idx.subs); |
|
313 | 313 | dimpos = find(strcmp(dims, onekey)); |
314 | 314 | if (~isempty(dimpos) && ~isempty(idxkey(i + 1).subs)) |
315 | 315 | nddata = length(dims); |
316 | | - indices = cell(1, nddata); |
317 | | - for j = 1:nddata |
318 | | - indices{j} = ':'; |
319 | | - end |
| 316 | + indices = repmat({':'}, 1, nddata); |
320 | 317 | indices{dimpos(1)} = idxkey(i + 1).subs{1}; |
321 | 318 | subsargs = struct('type', '()', 'subs', {indices}); |
322 | 319 | val = subsref(val, subsargs); |
|
330 | 327 | continue |
331 | 328 | end |
332 | 329 | end |
333 | | - escapedonekey = regexprep(onekey, '(?<=[^\\]|^)\.', '\\.'); |
| 330 | + escapedonekey = esckey_(onekey); |
334 | 331 | if (ischar(onekey) && ~isempty(onekey) && onekey(1) == char(36)) |
335 | 332 | val = obj.call_('jsonpath', val, onekey); |
336 | 333 | trackpath = escapedonekey; |
337 | 334 | elseif (isstruct(val)) |
338 | 335 | % check if struct array - if so, get field from all elements |
339 | | - if (numel(val) > 1 && isfield(val, onekey)) |
| 336 | + hasfield = isfield(val, onekey); |
| 337 | + if (numel(val) > 1 && hasfield) |
340 | 338 | % struct array - extract field from all elements |
341 | 339 | val = {val.(onekey)}; |
342 | 340 | if (all(cellfun(@isnumeric, val)) && all(cellfun(@(x) isequal(size(x), size(val{1})), val))) |
|
355 | 353 | i = i + 2; |
356 | 354 | continue |
357 | 355 | end |
358 | | - elseif isfield(val, onekey) |
| 356 | + elseif hasfield |
359 | 357 | % single struct with existing field |
360 | 358 | val = val.(onekey); |
361 | 359 | trackpath = [trackpath '.' escapedonekey]; |
|
364 | 362 | val = []; |
365 | 363 | trackpath = [trackpath '.' escapedonekey]; |
366 | 364 | end |
367 | | - elseif (isa(val, 'containers.Map') || isa(val, 'dictionary')) |
| 365 | + elseif (ismap_(obj.flags__, val)) |
368 | 366 | if isKey(val, onekey) |
369 | 367 | val = val(onekey); |
370 | 368 | else |
|
435 | 433 | else |
436 | 434 | onekey = idx.subs; |
437 | 435 | end |
438 | | - escapedonekey = regexprep(onekey, '(?<=[^\\]|^)\.', '\\.'); |
| 436 | + escapedonekey = esckey_(onekey); |
439 | 437 | if (ischar(onekey) && ~isempty(onekey)) |
440 | 438 | if (onekey(1) ~= char(36)) |
441 | 439 | temppath = [temppath '.' escapedonekey]; |
|
454 | 452 |
|
455 | 453 | % handle dimension-based assignment like jd.time(1:10) = newval |
456 | 454 | if (oplen >= 2 && strcmp(idxkey(oplen).type, '()')) |
457 | | - % check if second-to-last is a dimension name |
458 | 455 | if (strcmp(idxkey(oplen - 1).type, '.') && ischar(idxkey(oplen - 1).subs)) |
459 | 456 | dimname = idxkey(oplen - 1).subs; |
460 | 457 | % build path to the data |
|
467 | 464 | else |
468 | 465 | onekey = idx.subs; |
469 | 466 | end |
470 | | - escapedonekey = regexprep(onekey, '(?<=[^\\]|^)\.', '\\.'); |
| 467 | + escapedonekey = esckey_(onekey); |
471 | 468 | if (ischar(onekey) && ~isempty(onekey)) |
472 | 469 | if (onekey(1) ~= char(36)) |
473 | 470 | temppath = [temppath '.' escapedonekey]; |
|
484 | 481 | if (~isempty(dimpos) && ~isempty(idxkey(oplen).subs)) |
485 | 482 | % build full indices |
486 | 483 | nddata = length(dims); |
487 | | - indices = cell(1, nddata); |
488 | | - for j = 1:nddata |
489 | | - indices{j} = ':'; |
490 | | - end |
| 484 | + indices = repmat({':'}, 1, nddata); |
491 | 485 | indices{dimpos(1)} = idxkey(oplen).subs{1}; |
492 | 486 | % perform assignment |
493 | 487 | subsargs = struct('type', '()', 'subs', {indices}); |
|
506 | 500 | end |
507 | 501 | end |
508 | 502 |
|
| 503 | + % Fast path: single-level assignment like jd.key = value |
| 504 | + if (oplen == 1 && strcmp(idxkey(1).type, '.') && ischar(idxkey(1).subs)) |
| 505 | + fieldname = idxkey(1).subs; |
| 506 | + % Skip if JSONPath |
| 507 | + if (isempty(fieldname) || fieldname(1) ~= char(36)) |
| 508 | + if (isempty(obj.data)) |
| 509 | + obj.data = struct(); |
| 510 | + end |
| 511 | + if isstruct(obj.data) |
| 512 | + try |
| 513 | + obj.data.(fieldname) = otherobj; |
| 514 | + return |
| 515 | + catch |
| 516 | + % Field name invalid for struct, convert to Map |
| 517 | + fnames = fieldnames(obj.data); |
| 518 | + if (~isempty(fnames)) |
| 519 | + obj.data = containers.Map(fnames, struct2cell(obj.data), 'UniformValues', 0); |
| 520 | + else |
| 521 | + obj.data = containers.Map; |
| 522 | + end |
| 523 | + obj.data(fieldname) = otherobj; |
| 524 | + return |
| 525 | + end |
| 526 | + elseif ismap_(obj.flags__, obj.data) |
| 527 | + obj.data(fieldname) = otherobj; |
| 528 | + return |
| 529 | + end |
| 530 | + end |
| 531 | + end |
| 532 | + |
509 | 533 | oplen = length(idxkey); |
510 | 534 | opcell = cell (1, oplen + 1); |
511 | 535 | if (isempty(obj.data)) |
|
525 | 549 | end |
526 | 550 | if (ischar(idx.subs) && ~(~isempty(idx.subs) && idx.subs(1) == char(36))) |
527 | 551 | % Handle empty or non-struct/map data |
528 | | - if isempty(opcell{i}) || (~isstruct(opcell{i}) && ~isa(opcell{i}, 'containers.Map') && ~isa(opcell{i}, 'dictionary')) |
| 552 | + if isempty(opcell{i}) || (~isstruct(opcell{i}) && ~ismap_(obj.flags__, opcell{i})) |
529 | 553 | opcell{i} = obj.newkey_(); |
530 | 554 | end |
531 | | - if (((isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) && ~isKey(opcell{i}, idx.subs))) |
| 555 | + if (ismap_(obj.flags__, opcell{i}) && ~isKey(opcell{i}, idx.subs)) |
532 | 556 | idx.type = '()'; |
533 | 557 | opcell{i}(idx.subs) = obj.newkey_(); |
534 | 558 | elseif (isstruct(opcell{i}) && ~isfield(opcell{i}, idx.subs)) |
|
548 | 572 | end |
549 | 573 | if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36)) |
550 | 574 | opcell{i + 1} = obj.call_('jsonpath', opcell{i}, idx.subs); |
551 | | - elseif (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) |
| 575 | + elseif (ismap_(obj.flags__, opcell{i})) |
552 | 576 | opcell{i + 1} = opcell{i}(idx.subs); |
553 | 577 | else |
554 | 578 | opcell{i + 1} = subsref(opcell{i}, idx); |
555 | 579 | end |
556 | 580 | end |
557 | 581 |
|
558 | | - if (exist('OCTAVE_VERSION', 'builtin') ~= 0) && (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) |
| 582 | + if (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{i})) |
559 | 583 | opcell{i}(idx.subs) = otherobj; |
560 | 584 | opcell{end - 1} = opcell{i}; |
561 | 585 | else |
562 | 586 | if (ischar(idx.subs) && ~isempty(idx.subs) && idx.subs(1) == char(36)) |
563 | 587 | opcell{end - 1} = obj.call_('jsonpath', opcell{i}, idx.subs, otherobj); |
564 | 588 | else |
565 | | - if (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) |
| 589 | + if (ismap_(obj.flags__, opcell{i})) |
566 | 590 | idx = struct('type', '()', 'subs', idx.subs); |
567 | 591 | end |
568 | 592 | try |
|
576 | 600 |
|
577 | 601 | for i = oplen - 1:-1:1 |
578 | 602 | idx = idxkey(i); |
579 | | - if (ischar(idx.subs) && strcmp(idx.type, '.') && (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary'))) |
| 603 | + if (ischar(idx.subs) && strcmp(idx.type, '.') && ismap_(obj.flags__, opcell{i})) |
580 | 604 | idx.type = '()'; |
581 | 605 | end |
582 | 606 |
|
|
596 | 620 | opcell{i} = obj.call_('jsonpath', opcell{i}, idx.subs, opcell{i + 1}); |
597 | 621 | else |
598 | 622 | try |
599 | | - if (exist('OCTAVE_VERSION', 'builtin') ~= 0) && (isa(opcell{i}, 'containers.Map') || isa(opcell{i}, 'dictionary')) |
| 623 | + if (obj.flags__.isoctave_) && (ismap_(obj.flags__, opcell{i})) |
600 | 624 | opcell{i}(idx.subs) = opcell{i + 1}; |
601 | 625 | else |
602 | 626 | opcell{i} = subsasgn(opcell{i}, idx, opcell{i + 1}); |
|
624 | 648 | function val = keys(obj) |
625 | 649 | if (isstruct(obj.data)) |
626 | 650 | val = builtin('fieldnames', obj.data); |
627 | | - elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary')) |
| 651 | + elseif (ismap_(obj.flags__, obj.data)) |
628 | 652 | val = keys(obj.data); |
629 | 653 | else |
630 | 654 | val = 1:length(obj.data); |
631 | 655 | end |
632 | | - val = val(:); |
633 | 656 | end |
634 | 657 |
|
635 | 658 | function val = fieldnames(obj) |
|
641 | 664 | % list subfields at the current level |
642 | 665 | if (isstruct(obj.data)) |
643 | 666 | val = isfield(obj.data, key); |
644 | | - elseif (isa(obj.data, 'containers.Map') || isa(obj.data, 'dictionary')) |
| 667 | + elseif (ismap_(obj.flags__, obj.data)) |
645 | 668 | val = isKey(obj.data, key); |
646 | 669 | else |
647 | 670 | val = (key < length(obj.data)); |
|
675 | 698 |
|
676 | 699 | % internal: insert new key if does not exist |
677 | 700 | function val = newkey_(obj) |
678 | | - if (exist('containers.Map')) |
| 701 | + if (obj.flags__.hasmap_) |
679 | 702 | val = containers.Map; |
680 | 703 | else |
681 | 704 | val = struct; |
|
1027 | 1050 |
|
1028 | 1051 | end |
1029 | 1052 | end |
| 1053 | + |
| 1054 | +%% ======================================================================== |
| 1055 | +%% Helper functions (outside class for persistent caching) |
| 1056 | +%% ======================================================================== |
| 1057 | + |
| 1058 | +% cached platform flags - called once per session |
| 1059 | +function flags = getflags_() |
| 1060 | + persistent cachedflags |
| 1061 | + if isempty(cachedflags) |
| 1062 | + cachedflags = struct(); |
| 1063 | + cachedflags.builtinjson = 0; |
| 1064 | + cachedflags.isoctave_ = exist('OCTAVE_VERSION', 'builtin') ~= 0; |
| 1065 | + try |
| 1066 | + containers.Map(); |
| 1067 | + cachedflags.hasmap_ = true; |
| 1068 | + catch |
| 1069 | + cachedflags.hasmap_ = false; |
| 1070 | + end |
| 1071 | + try |
| 1072 | + dictionary(); |
| 1073 | + cachedflags.hasdict_ = true; |
| 1074 | + catch |
| 1075 | + cachedflags.hasdict_ = false; |
| 1076 | + end |
| 1077 | + end |
| 1078 | + flags = cachedflags; |
| 1079 | +end |
| 1080 | + |
| 1081 | +% merge two structs |
| 1082 | +function s = mergestruct_(base, override) |
| 1083 | + s = base; |
| 1084 | + fnames = fieldnames(override); |
| 1085 | + for i = 1:length(fnames) |
| 1086 | + s.(fnames{i}) = override.(fnames{i}); |
| 1087 | + end |
| 1088 | +end |
| 1089 | + |
| 1090 | +% check if value is a map type |
| 1091 | +function tf = ismap_(flags, val) |
| 1092 | + tf = isa(val, 'containers.Map') || (flags.hasdict_ && isa(val, 'dictionary')); |
| 1093 | +end |
| 1094 | + |
| 1095 | +% escape dots in key for JSONPath |
| 1096 | +function escaped = esckey_(key) |
| 1097 | + escaped = regexprep(key, '(?<=[^\\]|^)\.', '\\.'); |
| 1098 | +end |
0 commit comments