@yuneta/gobj-js 7.25.3 → 7.25.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -534,7 +534,22 @@ kw_get_dict_value(gobj, kw, path, default_value, flag)
534
534
  // kw_get_bool(gobj, {x: "false"}, "x", true, 0) → true (not a boolean: logged)
535
535
  // kw_get_bool(gobj, {x: "false"}, "x", true, KW_WILD_NUMBER) → false
536
536
  // kw_get_bool() logs a value that is not a boolean even without
537
- // KW_REQUIRED ("path MUST BE a json boolean"), as the C reader does.
537
+ // KW_REQUIRED ("path MUST BE a json boolean"), as the C reader does,
538
+ // and so do kw_get_int() / kw_get_real() for a value that is not a
539
+ // number ("path MUST BE a json integer" / "... a json real"):
540
+ // kw_get_int(gobj, {x: 3.9}, "x", 0, 0) → 3 (truncated, as C)
541
+ // kw_get_int(gobj, {x: "12"}, "x", 5, 0) → 5 (not a number: logged)
542
+ // kw_get_int(gobj, {x: "0x1F"}, "x", 5, KW_WILD_NUMBER) → 31 (strtoll base 0)
543
+ // kw_get_bool(gobj, {x: "0x1F"}, "x", true, KW_WILD_NUMBER) → false (atoi: decimal)
544
+ // kw_get_str() logs as C too: a value that is there and is not a
545
+ // string, required or not ("path MUST BE a json str"), never a null
546
+ // value, and with KW_REQUIRED a path that is not there:
547
+ // kw_get_str(gobj, {x: 5}, "x", "d", 0) → "d" (logged)
548
+ // kw_get_str(gobj, {x: null}, "x", "d", KW_REQUIRED) → "d" (not logged)
549
+ // kw_get_dict() / kw_get_list() log a value of another type only with
550
+ // KW_REQUIRED ("path MUST BE a json dict" / "... a json list"), as C.
551
+ // KW_EXTRACT takes out only a value the reader answers with: a value
552
+ // of another type stays in the kw.
538
553
 
539
554
  kw_set_dict_value(gobj, kw, path, value)
540
555
  kw_set_subdict_value(gobj, kw, path, key, value)
@@ -3032,7 +3032,7 @@ function gobj_stats(gobj, stats, kw, src) {
3032
3032
  *
3033
3033
  * Licence: MIT (http://www.opensource.org/licenses/mit-license)
3034
3034
  * Copyright (c) 2014,2024 Niyamaka.
3035
- * Copyright (c) 2025, ArtGins.
3035
+ * Copyright (c) 2025-2026, ArtGins.
3036
3036
  *********************************************************************************/
3037
3037
  var kw_flag_t = Object.freeze({
3038
3038
  KW_REQUIRED: 1,
@@ -3608,10 +3608,13 @@ function kw_delete(gobj, kw, path) {
3608
3608
  * anything else gives the default back, LOGGED ("path MUST BE a
3609
3609
  * json boolean", required or not) -- unless KW_WILD_NUMBER asks
3610
3610
  * to read a number (0 is false), a string ("true"/"false" in any
3611
- * case, else its integer) or null (false); a list or a dict is
3612
- * then false, logged too. Before gobj-js 7.25.2 the value went
3613
- * through Boolean(), so "false" was TRUE; before 7.25.3 a value
3614
- * of the wrong type gave the default back in silence.
3611
+ * case, else its decimal integer as atoi() reads it: "0x1F" is 0,
3612
+ * so false) or null (false); a list or a dict is then false,
3613
+ * logged too. Before gobj-js 7.25.2 the value went through
3614
+ * Boolean(), so "false" was TRUE; before 7.25.3 a value of the
3615
+ * wrong type gave the default back in silence; before 7.25.5 the
3616
+ * string went through parseInt() with no base, which reads hex:
3617
+ * "0x1F" was TRUE.
3615
3618
  ************************************************************/
3616
3619
  function kw_get_bool(gobj, kw, path, default_value, flag) {
3617
3620
  if (gobj && !is_gobj(gobj)) log_error(`GObj bad instanceof`);
@@ -3637,7 +3640,7 @@ function kw_get_bool(gobj, kw, path, default_value, flag) {
3637
3640
  else if (wild && is_string(b)) {
3638
3641
  if (b.toLowerCase() === "true") value = true;
3639
3642
  else if (b.toLowerCase() === "false") value = false;
3640
- else value = (parseInt(b) || 0) !== 0;
3643
+ else value = (parseInt(b, 10) || 0) !== 0;
3641
3644
  } else if (wild && b === null) value = false;
3642
3645
  else if (wild) {
3643
3646
  log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a simple json element: '${path}'`);
@@ -3652,7 +3655,34 @@ function kw_get_bool(gobj, kw, path, default_value, flag) {
3652
3655
  return value;
3653
3656
  }
3654
3657
  /************************************************************
3655
- *
3658
+ * The integer at the start of a string, as C's strtoll(s, 0, 0)
3659
+ * reads it: leading spaces, a sign, then "0x" for hex, "0" for
3660
+ * octal, else decimal, up to the first character that is not a
3661
+ * digit of that base. 0 when there is no digit.
3662
+ ************************************************************/
3663
+ function strtoll_base0(s) {
3664
+ const m = /^\s*([+-]?)(0[xX][0-9a-fA-F]+|0[0-7]*|[1-9][0-9]*)/.exec(s);
3665
+ if (!m) return 0;
3666
+ let digits = m[2];
3667
+ let value;
3668
+ if (digits.length > 1 && (digits[1] === "x" || digits[1] === "X")) value = parseInt(digits.substring(2), 16);
3669
+ else if (digits[0] === "0") value = parseInt(digits, 8);
3670
+ else value = parseInt(digits, 10);
3671
+ return m[1] === "-" ? -value : value;
3672
+ }
3673
+ /************************************************************
3674
+ * As the C kw_get_int(): a number found is the answer,
3675
+ * truncated toward zero as C casts a real, and anything else
3676
+ * gives the default back (as an integer), LOGGED ("path MUST BE
3677
+ * a json integer", required or not) -- unless KW_WILD_NUMBER
3678
+ * asks to read a boolean (1/0), a string (as strtoll() with base
3679
+ * 0: "0x1F" is 31, "017" is 15, "12abc" is 12, "abc" is 0) or
3680
+ * null (0); a list or a dict is then 0, logged too. KW_EXTRACT
3681
+ * takes out only a value the reader answers with.
3682
+ * Before gobj-js 7.25.4 KW_EXTRACT deleted the value before its
3683
+ * type was looked at, a value of another type was logged only
3684
+ * with KW_REQUIRED, KW_WILD_NUMBER was ignored, and a number went
3685
+ * through parseInt(), which reads it as a string: 1e-7 gave 1.
3656
3686
  ************************************************************/
3657
3687
  function kw_get_int(gobj, kw, path, default_value, flag) {
3658
3688
  if (gobj && !is_gobj(gobj)) log_error(`GObj bad instanceof`);
@@ -3660,6 +3690,7 @@ function kw_get_int(gobj, kw, path, default_value, flag) {
3660
3690
  const required = flag && flag & kw_flag_t.KW_REQUIRED;
3661
3691
  const create = flag && flag & kw_flag_t.KW_CREATE;
3662
3692
  const extract = flag && flag & kw_flag_t.KW_EXTRACT;
3693
+ const wild = flag && flag & kw_flag_t.KW_WILD_NUMBER;
3663
3694
  if (v === void 0) {
3664
3695
  if (create) {
3665
3696
  let v = parseInt(default_value);
@@ -3671,18 +3702,34 @@ function kw_get_int(gobj, kw, path, default_value, flag) {
3671
3702
  }
3672
3703
  return parseInt(default_value);
3673
3704
  }
3674
- if (extract) kw_delete(gobj, kw, path);
3675
- if (!is_number(v)) {
3676
- if (required) {
3677
- log_error(`path value MUST BE a number: ${path}`);
3678
- trace_msg(kw);
3679
- }
3705
+ let value;
3706
+ if (is_number(v)) value = Math.trunc(v);
3707
+ else if (!wild) {
3708
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a json integer: '${path}'`);
3709
+ trace_json(kw);
3680
3710
  return parseInt(default_value);
3711
+ } else if (is_boolean(v)) value = v ? 1 : 0;
3712
+ else if (is_string(v)) value = strtoll_base0(v);
3713
+ else if (v === null) value = 0;
3714
+ else {
3715
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a simple json element: '${path}'`);
3716
+ trace_json(kw);
3717
+ return 0;
3681
3718
  }
3682
- return parseInt(v);
3719
+ if (extract) kw_delete(gobj, kw, path);
3720
+ return value;
3683
3721
  }
3684
3722
  /************************************************************
3685
- *
3723
+ * As the C kw_get_real(): a number found is the answer, and
3724
+ * anything else gives the default back (as a number), LOGGED
3725
+ * ("path MUST BE a json real", required or not) -- unless
3726
+ * KW_WILD_NUMBER asks to read a boolean (1/0), a string (as
3727
+ * parseFloat() reads it, 0 when it does not start with a number)
3728
+ * or null (0); a list or a dict is then 0, logged too.
3729
+ * KW_EXTRACT takes out only a value the reader answers with.
3730
+ * Before gobj-js 7.25.4 KW_EXTRACT deleted the value before its
3731
+ * type was looked at, a value of another type was logged only
3732
+ * with KW_REQUIRED, and KW_WILD_NUMBER was ignored.
3686
3733
  ************************************************************/
3687
3734
  function kw_get_real(gobj, kw, path, default_value, flag) {
3688
3735
  if (gobj && !is_gobj(gobj)) log_error(`GObj bad instanceof`);
@@ -3690,6 +3737,7 @@ function kw_get_real(gobj, kw, path, default_value, flag) {
3690
3737
  const required = flag && flag & kw_flag_t.KW_REQUIRED;
3691
3738
  const create = flag && flag & kw_flag_t.KW_CREATE;
3692
3739
  const extract = flag && flag & kw_flag_t.KW_EXTRACT;
3740
+ const wild = flag && flag & kw_flag_t.KW_WILD_NUMBER;
3693
3741
  if (v === void 0) {
3694
3742
  if (create) {
3695
3743
  let v = Number(default_value);
@@ -3701,15 +3749,39 @@ function kw_get_real(gobj, kw, path, default_value, flag) {
3701
3749
  }
3702
3750
  return Number(default_value);
3703
3751
  }
3704
- if (extract) kw_delete(gobj, kw, path);
3705
- if (!is_number(v)) {
3706
- if (required) {
3707
- log_error(`path value MUST BE a number: ${path}`);
3708
- trace_msg(kw);
3709
- }
3752
+ let value;
3753
+ if (is_number(v)) value = v;
3754
+ else if (!wild) {
3755
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a json real: '${path}'`);
3756
+ trace_json(kw);
3710
3757
  return Number(default_value);
3758
+ } else if (is_boolean(v)) value = v ? 1 : 0;
3759
+ else if (is_string(v)) {
3760
+ value = parseFloat(v);
3761
+ if (Number.isNaN(value)) value = 0;
3762
+ } else if (v === null) value = 0;
3763
+ else {
3764
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a simple json element: '${path}'`);
3765
+ trace_json(kw);
3766
+ return 0;
3767
+ }
3768
+ if (extract) kw_delete(gobj, kw, path);
3769
+ return value;
3770
+ }
3771
+ /************************************************************
3772
+ * TRUE when the last key of `path` is a key of its dict, whatever
3773
+ * its value: kw_find_path() answers undefined both for a key that
3774
+ * is not there and for one whose value is undefined.
3775
+ ************************************************************/
3776
+ function kw_path_holds_key(kw, path) {
3777
+ let ss = is_string(path) ? path.split("`") : [];
3778
+ let parent = kw;
3779
+ for (let i = 0; i < ss.length - 1; i++) {
3780
+ if (!(is_object(parent) || is_array(parent))) return false;
3781
+ parent = parent[ss[i]];
3711
3782
  }
3712
- return Number(v);
3783
+ if (!(is_object(parent) || is_array(parent)) || ss.length === 0) return false;
3784
+ return Object.prototype.hasOwnProperty.call(parent, ss[ss.length - 1]);
3713
3785
  }
3714
3786
  /************************************************************
3715
3787
  * The default comes back AS GIVEN, as in the C kw_get_str()
@@ -3718,6 +3790,17 @@ function kw_get_real(gobj, kw, path, default_value, flag) {
3718
3790
  * TRUE: an `if(value)` on the answer took an absent key for a
3719
3791
  * present one. KW_CREATE stores a string default, and null for
3720
3792
  * any other, as C stores json_null() for a NULL default.
3793
+ * KW_EXTRACT takes out only a string (before gobj-js 7.25.4 it
3794
+ * deleted any value, and a value that was not a string was lost).
3795
+ * C refuses to extract a string at all ("Cannot extract a
3796
+ * string": its answer would point into the freed json); a JS
3797
+ * string outlives the kw, so here it is taken out.
3798
+ * A value that is there and is not a string gives the default
3799
+ * back, LOGGED ("path MUST BE a json str", required or not), as
3800
+ * C does; a null value is the key saying "no string" and is not
3801
+ * logged, and KW_REQUIRED logs only a path that is not there at
3802
+ * all. Before gobj-js 7.25.5 a value of another type was logged
3803
+ * only with KW_REQUIRED, and then a null one too.
3721
3804
  ************************************************************/
3722
3805
  function kw_get_str(gobj, kw, path, default_value, flag) {
3723
3806
  if (gobj && !is_gobj(gobj)) log_error(`GObj bad instanceof`);
@@ -3729,20 +3812,20 @@ function kw_get_str(gobj, kw, path, default_value, flag) {
3729
3812
  if (create) {
3730
3813
  kw_set_dict_value(gobj, kw, path, is_string(default_value) ? default_value : null);
3731
3814
  return default_value;
3732
- } else if (required) {
3815
+ } else if (required && !kw_path_holds_key(kw, path)) {
3733
3816
  log_error(`path not found: '${path}'`);
3734
3817
  trace_json(kw);
3735
3818
  }
3736
3819
  return default_value;
3737
3820
  }
3738
- if (extract) kw_delete(gobj, kw, path);
3739
3821
  if (!is_string(v)) {
3740
- if (required) {
3741
- log_error(`${gobj_short_name(gobj)}: path value MUST BE a string: ${path}`);
3742
- trace_msg(kw);
3822
+ if (v !== null) {
3823
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a json str: '${path}'`);
3824
+ trace_json(kw);
3743
3825
  }
3744
3826
  return default_value;
3745
3827
  }
3828
+ if (extract) kw_delete(gobj, kw, path);
3746
3829
  return v;
3747
3830
  }
3748
3831
  /************************************************************
@@ -3794,8 +3877,8 @@ function kw_get_dict(gobj, kw, path, default_value, flag) {
3794
3877
  }
3795
3878
  if (!is_object(v)) {
3796
3879
  if (required) {
3797
- log_error(`${gobj_short_name(gobj)}: path value MUST BE a dict: ${path}`);
3798
- trace_msg(kw);
3880
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a json dict: '${path}'`);
3881
+ trace_json(kw);
3799
3882
  }
3800
3883
  return default_value;
3801
3884
  }
@@ -3852,8 +3935,8 @@ function kw_get_list(gobj, kw, path, default_value, flag) {
3852
3935
  }
3853
3936
  if (!is_array(v)) {
3854
3937
  if (required) {
3855
- log_error(`${gobj_short_name(gobj)}: path value MUST BE an array: ${path}`);
3856
- trace_msg(kw);
3938
+ log_error(`${gobj ? gobj_short_name(gobj) + ": " : ""}path MUST BE a json list: '${path}'`);
3939
+ trace_json(kw);
3857
3940
  }
3858
3941
  return default_value;
3859
3942
  }