-- ====================================================================
-- co_2026_03_16_01_COMUN_PLJSON.sql
-- Instaleaza PL/JSON (minimal core) in schema CONTAFIN_ORACLE
-- cu GRANT EXECUTE si PUBLIC SYNONYM pentru acces din alte scheme
--
-- Rulare: sqlplus CONTAFIN_ORACLE/password@ROA_ROMFAST @co_2026_03_10_02_COMUN_PLJSON.sql
--
-- Sursa: https://github.com/pljson/pljson (MIT License)
-- Versiune: 3.7.1 (core minimal - fara addons)
-- Script self-contained - nu necesita fisiere externe
-- ====================================================================

SET SERVEROUTPUT ON SIZE UNLIMITED
SET DEFINE OFF
ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 2;

PROMPT;
PROMPT =============================================;
PROMPT  PL/JSON - Instalare in CONTAFIN_ORACLE;
PROMPT =============================================;
PROMPT;

-- ====================================================================
-- STEP 1: Cleanup - sterge obiectele existente (safe)
-- ====================================================================
PROMPT [1/6] Cleanup obiecte existente...;

BEGIN
  -- Packages
  BEGIN EXECUTE IMMEDIATE 'DROP PACKAGE pljson_parser'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PACKAGE pljson_printer'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PACKAGE pljson_ext'; EXCEPTION WHEN OTHERS THEN NULL; END;
  -- Public synonyms
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_list'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_element'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_element_array'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_string'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_number'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_bool'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_null'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_varray'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_narray'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_parser'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_printer'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_ext'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_path_segment'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM pljson_path'; EXCEPTION WHEN OTHERS THEN NULL; END;
  -- Types (ordinea conteaza - dependentele ultimele)
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_list FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_string FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_number FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_bool FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_null FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_element_array FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_element FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_path_segment FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_path FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_narray FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
  BEGIN EXECUTE IMMEDIATE 'DROP TYPE pljson_varray FORCE'; EXCEPTION WHEN OTHERS THEN NULL; END;
END;
/

-- ====================================================================
-- STEP 2: Creare tipuri si declaratii (in ordinea dependentelor)
-- ====================================================================
PROMPT [2/6] Creare tipuri de baza...;


-- --- pljson_element.type.decl ---
create or replace type pljson_path_segment as object (
  indx number(32),
  name varchar2(4000)
) final
/

create or replace type pljson_path as table of pljson_path_segment
/

create or replace type pljson_element force as object
(
  /* 1 = object, 2 = array, 3 = string, 4 = number, 5 = bool, 6 = null */
  typeval number(1),
  mapname varchar2(4000),
  mapindx number(32),
  object_id number,

  /* not instantiable */
  constructor function pljson_element return self as result,

  member function is_object return boolean,
  member function is_array return boolean,
  member function is_string return boolean,
  member function is_number return boolean,
  member function is_bool return boolean,
  member function is_null return boolean,
  member function get_type return varchar2,
  /* should be overriden */
  member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2,

  /*
    member methods to remove need for treat()
  */
  member function get_string(max_byte_size number default null, max_char_size number default null) return varchar2,
  member function get_clob return clob,
  member function get_number return number,
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers */
  member function get_double return binary_double,
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers, is_number is still true, extra info */
  /* return true if 'number' is representable by Oracle number */
  /** Private method for internal processing. */
  member function is_number_repr_number return boolean,
  /* return true if 'number' is representable by Oracle binary_double */
  /** Private method for internal processing. */
  member function is_number_repr_double return boolean,
  member function get_bool return boolean,

  member function count return number,
  member function get(pair_name varchar2) return pljson_element,
  member function get(position pls_integer) return pljson_element,

  member function path(json_path varchar2, base number default 1) return pljson_element,

  /* output methods */
  member function to_char(spaces boolean default true, chars_per_line number default 0) return varchar2,
  member procedure to_clob(self in pljson_element, buf in out nocopy clob, spaces boolean default false, chars_per_line number default 0, erase_clob boolean default true),
  member procedure print(self in pljson_element, spaces boolean default true, chars_per_line number default 8192, jsonp varchar2 default null),
  member procedure htp(self in pljson_element, spaces boolean default false, chars_per_line number default 0, jsonp varchar2 default null),

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  member procedure get_internal_path(self in pljson_element, path pljson_path, path_position pls_integer, ret out nocopy pljson_element),
  
  /* private method for internal use, not part of the API, contributed by @asfernandes */
  member function put_internal_path(self in out nocopy pljson_element, path pljson_path, elem pljson_element, path_position pls_integer) return boolean
) not final
/
show err

create or replace type pljson_element_array as table of pljson_element
/

-- --- pljson_list.type.decl ---
set termout off
create or replace type pljson_varray as table of varchar2(32767);
/
create or replace type pljson_narray as table of number;
/

set termout on
create or replace type pljson_list force under pljson_element (

  /*
  Copyright (c) 2010 Jonas Krogsboell

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */

  /* variables */
  list_data pljson_element_array,

  /* constructors */
  constructor function pljson_list return self as result,
  constructor function pljson_list(str varchar2) return self as result,
  constructor function pljson_list(str clob) return self as result,
  constructor function pljson_list(str blob, charset varchar2 default 'UTF8') return self as result,
  constructor function pljson_list(str_array pljson_varray) return self as result,
  constructor function pljson_list(num_array pljson_narray) return self as result,
  constructor function pljson_list(elem pljson_element) return self as result,
  constructor function pljson_list(elem_array pljson_element_array) return self as result,
  overriding member function is_array return boolean,
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2,

  /* list management */
  member procedure append(self in out nocopy pljson_list, elem pljson_element, position pls_integer default null),
  member procedure append(self in out nocopy pljson_list, elem varchar2, position pls_integer default null),
  member procedure append(self in out nocopy pljson_list, elem clob, position pls_integer default null),
  member procedure append(self in out nocopy pljson_list, elem number, position pls_integer default null),
  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure append(self in out nocopy pljson_list, elem binary_double, position pls_integer default null),
  member procedure append(self in out nocopy pljson_list, elem boolean, position pls_integer default null),
  member procedure append(self in out nocopy pljson_list, elem pljson_list, position pls_integer default null),

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem pljson_element),
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem varchar2),
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem clob),
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem number),
  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem binary_double),
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem boolean),
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem pljson_list),

  member procedure remove(self in out nocopy pljson_list, position pls_integer),
  member procedure remove_first(self in out nocopy pljson_list),
  member procedure remove_last(self in out nocopy pljson_list),

  overriding member function count return number,
  overriding member function get(position pls_integer) return pljson_element,
  member function get_string(position pls_integer) return varchar2,
  member function get_clob(position pls_integer) return clob,
  member function get_number(position pls_integer) return number,
  member function get_double(position pls_integer) return binary_double,
  member function get_bool(position pls_integer) return boolean,
  member function get_pljson_list(position pls_integer) return pljson_list,
  member function head return pljson_element,
  member function last return pljson_element,
  member function tail return pljson_list,

  /* json path */
  overriding member function path(json_path varchar2, base number default 1) return pljson_element,
  /* json path_put */
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem pljson_element, base number default 1),
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem varchar2, base number default 1),
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem clob, base number default 1),
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem number, base number default 1),
  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem binary_double, base number default 1),
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem boolean, base number default 1),
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem pljson_list, base number default 1),

  /* json path_remove */
  member procedure path_remove(self in out nocopy pljson_list, json_path varchar2, base number default 1),

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member procedure get_internal_path(self in pljson_list, path pljson_path, path_position pls_integer, ret out nocopy pljson_element),
  
  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member function put_internal_path(self in out nocopy pljson_list, path pljson_path, elem pljson_element, path_position pls_integer) return boolean
) not final;
/
show err

set termout on
create or replace type pljson force under pljson_element (

  /*
  Copyright (c) 2010 Jonas Krogsboell

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */

  /* variables */
  json_data pljson_element_array,
  check_for_duplicate number,

  /* constructors */
  constructor function pljson return self as result,
  constructor function pljson(str varchar2) return self as result,
  constructor function pljson(str in clob) return self as result,
  constructor function pljson(str in blob, charset varchar2 default 'UTF8') return self as result,
  constructor function pljson(str_array pljson_varray) return self as result,
  constructor function pljson(elem pljson_element) return self as result,
  constructor function pljson(l pljson_list) return self as result,
  overriding member function is_object return boolean,
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2,

  /* member management */
  member procedure remove(pair_name varchar2),

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value pljson_element, position pls_integer default null),
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value varchar2, position pls_integer default null),
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value clob, position pls_integer default null),
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value number, position pls_integer default null),
  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value binary_double, position pls_integer default null),
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value boolean, position pls_integer default null),

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value pljson, position pls_integer default null),
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value pljson_list, position pls_integer default null),

  overriding member function count return number,
  overriding member function get(pair_name varchar2) return pljson_element,

  member function get_string(pair_name varchar2) return varchar2,
  member function get_clob(pair_name varchar2) return clob,
  member function get_number(pair_name varchar2) return number,
  member function get_double(pair_name varchar2) return binary_double,
  member function get_bool(pair_name varchar2) return boolean,
  member function get_pljson(pair_name varchar2) return pljson,
  member function get_pljson_list(pair_name varchar2) return pljson_list,

  overriding member function get(position pls_integer) return pljson_element,
  member function index_of(pair_name varchar2) return number,
  member function exist(pair_name varchar2) return boolean,

  member procedure check_duplicate(self in out nocopy pljson, v_set boolean),
  member procedure remove_duplicates(self in out nocopy pljson),

  /* json path */
  overriding member function path(json_path varchar2, base number default 1) return pljson_element,
  
  /* json path_put */
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem pljson_element, base number default 1),
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem varchar2, base number default 1),
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem clob, base number default 1),
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem number, base number default 1),
  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem binary_double, base number default 1),
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem boolean, base number default 1),
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem pljson, base number default 1),
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem pljson_list, base number default 1),

  /* json path_remove */
  member procedure path_remove(self in out nocopy pljson, json_path varchar2, base number default 1),

  /* map functions */
  member function get_keys return pljson_list,
  member function get_values return pljson_list,

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member procedure get_internal_path(self in pljson, path pljson_path, path_position pls_integer, ret out nocopy pljson_element),
  
  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member function put_internal_path(self in out nocopy pljson, path pljson_path, elem pljson_element, path_position pls_integer) return boolean
) not final;
/
show err

-- --- pljson_string.type (spec + body) ---
create or replace type pljson_string force under pljson_element (

  num number,
  str varchar2(32767),
  extended_str clob,
  unescaped_string_delim_p number, -- 0/1 for false/true
  unescaped_string_delim varchar2(10),

  constructor function pljson_string(str varchar2, esc boolean default true,
    unescaped_string_delim_p boolean default false, unescaped_string_delim varchar2 default '') return self as result,
  constructor function pljson_string(str clob, esc boolean default true,
    unescaped_string_delim_p boolean default false, unescaped_string_delim varchar2 default '') return self as result,
  overriding member function is_string return boolean,
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2,

  overriding member function get_string(max_byte_size number default null, max_char_size number default null) return varchar2,
  overriding member function get_clob return clob
  /*
  member procedure get_string(buf in out nocopy clob)
  */
) not final
/
show err

create or replace type body pljson_string as

  constructor function pljson_string(str varchar2, esc boolean default true,
    unescaped_string_delim_p boolean default false, unescaped_string_delim varchar2 default '') return self as result as
  begin
    self.typeval := 3;
    if (esc) then self.num := 1; else self.num := 0; end if; --message to pretty printer
    self.str := str;
    -- unescaped string delimiter
    self.unescaped_string_delim_p := 0;
    if unescaped_string_delim_p then
      self.unescaped_string_delim_p := 1;
      self.unescaped_string_delim := unescaped_string_delim;
    end if;
    return;
  end;

  constructor function pljson_string(str clob, esc boolean default true,
    unescaped_string_delim_p boolean default false, unescaped_string_delim varchar2 default '') return self as result as
    /* E.I.Sarmas (github.com/dsnz)   2016-01-21   limit to 5000 chars */
    /* for Unicode text, varchar2 'self.str' not exceed 5000 chars, does not limit size of data */
    max_string_chars number := 5000; /* chunk size, less than this number may be copied */
    lengthcc number;
  begin
    self.typeval := 3;
    if (esc) then self.num := 1; else self.num := 0; end if; --message to pretty printer
    -- lengthcc := pljson_parser.lengthcc(str);
    -- if lengthcc > max_string_chars then
    /* not so accurate, may be less than "max_string_chars" */
    /* it's not absolute restricting limit and it's faster than using lengthcc */
    if (dbms_lob.getlength(str) > max_string_chars) then
      self.extended_str := str;
    end if;
    -- GHS 20120615: Added IF structure to handle null clobs
    if dbms_lob.getlength(str) > 0 then
      /* may read less than "max_string_chars" characters but it's a sample so doesn't matter */
      dbms_lob.read(str, max_string_chars, 1, self.str);
    end if;
    -- unescaped string delimiter
    self.unescaped_string_delim_p := 0;
    if unescaped_string_delim_p then
      self.unescaped_string_delim_p := 1;
      self.unescaped_string_delim := unescaped_string_delim;
    end if;
    return;
  end;

  overriding member function is_string return boolean as
  begin
    return true;
  end;

  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    return get_string(max_byte_size, max_char_size);
  end;

  overriding member function get_string(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    if (max_byte_size is not null) then
      return substrb(self.str, 1, max_byte_size);
    elsif (max_char_size is not null) then
      return substr(self.str, 1, max_char_size);
    else
      return self.str;
    end if;
  end;

  overriding member function get_clob return clob as
  begin
    if (extended_str is not null) then
      --dbms_lob.copy(buf, extended_str, dbms_lob.getlength(extended_str));
      return self.extended_str;
    else
      /* writeappend works with length2() value */
      --dbms_lob.writeappend(buf, length2(self.str), self.str);
      return self.str;
    end if;
  end;

  /*
  member procedure get_string(buf in out nocopy clob) as
  begin
    dbms_lob.trim(buf, 0);
    if (extended_str is not null) then
      dbms_lob.copy(buf, extended_str, dbms_lob.getlength(extended_str));
    else
      -- writeappend works with length2() value
      dbms_lob.writeappend(buf, length2(self.str), self.str);
    end if;
  end;
  */
end;
/
show err

-- --- pljson_number.type (spec + body) ---
create or replace type pljson_number force under pljson_element
(
  num number,
  num_double binary_double, -- both num and num_double are set, there is never exception (until Oracle 12c)
  num_repr_number_p varchar2(1),
  num_repr_double_p varchar2(1),
  
  constructor function pljson_number(num number) return self as result,
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers */
  constructor function pljson_number(num_double binary_double) return self as result,
  overriding member function is_number return boolean,
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2,
  
  overriding member function get_number return number,
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers */
  overriding member function get_double return binary_double,
  
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers, is_number is still true, extra info */
  /* return true if 'number' is representable by Oracle number */
  /** Private method for internal processing. */
  overriding member function is_number_repr_number return boolean,
  /* return true if 'number' is representable by Oracle binary_double */
  /** Private method for internal processing. */
  overriding member function is_number_repr_double return boolean,
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers */
  -- set value for number from string representation; to replace to_number in pljson_parser
  -- can automatically decide and use binary_double if needed
  -- less confusing than new constructor with dummy argument for overloading
  -- centralized parse_number to use everywhere else and replace code in pljson_parser
  -- this procedure is meant to be used internally only
  -- procedure does not work correctly if called standalone in locales that
  -- use a character other than "." for decimal point
  member procedure parse_number(str varchar2),

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  -- this procedure is meant to be used internally only
  member function number_toString return varchar2
) not final
/
show err

create or replace type body pljson_number as

 constructor function pljson_number(num number) return self as result as
  begin
    self.typeval := 4;
    self.num := nvl(num, 0);
    /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers; typeval not changed, it is still json number */
    self.num_repr_number_p := 't';
    self.num_double := num;
    if (to_number(self.num_double) = self.num) then
      self.num_repr_double_p := 't';
    else
      self.num_repr_double_p := 'f';
    end if;
    return;
  end;
  
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers; typeval not changed, it is still json number */
  constructor function pljson_number(num_double binary_double) return self as result as
  begin
    self.typeval := 4;
    self.num_double := nvl(num_double, 0);
    self.num_repr_double_p := 't';
    self.num := num_double;
    if (to_binary_double(self.num) = self.num_double) then
      self.num_repr_number_p := 't';
    else
      self.num_repr_number_p := 'f';
    end if;
    return;
  end;
  
  overriding member function is_number return boolean as
  begin
    return true;
  end;
  
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    return self.num;
  end;
  
  overriding member function get_number return number as
  begin
    return self.num;
  end;
  
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers */
  overriding member function get_double return binary_double as
  begin
    return self.num_double;
  end;
  
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers, is_number is still true, extra check */
  /* return true if 'number' is representable by Oracle number */
  overriding member function is_number_repr_number return boolean is
  begin
    return (num_repr_number_p = 't');
  end;
  
  /* return true if 'number' is representable by Oracle binary_double */
  overriding member function is_number_repr_double return boolean is
  begin
    return (num_repr_double_p = 't');
  end;
  
  /* E.I.Sarmas (github.com/dsnz)   2016-11-03   support for binary_double numbers */
  -- set value for number from string representation; to replace to_number in pljson_parser
  -- can automatically decide and use binary_double if needed (set repr variables)
  -- underflows and overflows count as representable if happen on both type representations
  -- less confusing than new constructor with dummy argument for overloading
  -- centralized parse_number to use everywhere else and replace code in pljson_parser
  --
  -- WARNING:
  --
  -- procedure does not work correctly if called standalone in locales that
  -- use a character other than "." for decimal point
  --
  -- parse_number() is intended to be used inside pljson_parser which
  -- uses session NLS_PARAMETERS to get decimal point and
  -- changes "." to this decimal point before calling parse_number()
  --
  member procedure parse_number(str varchar2) is
  begin
    self.num := to_number(str);
    self.num_repr_number_p := 't';
    self.num_double := to_binary_double(str);
    self.num_repr_double_p := 't';
    if (to_binary_double(self.num) != self.num_double) then
      self.num_repr_number_p := 'f';
    end if;
    if (to_number(self.num_double) != self.num) then
      self.num_repr_double_p := 'f';
    end if;
  end parse_number;
  
  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  -- centralized toString to use everywhere else and replace code in pljson_printer
  member function number_toString return varchar2 is
    num number;
    num_double binary_double;
    buf varchar2(4000);
  begin
    /* unrolled, instead of using two nested fuctions for speed */
    if (self.num_repr_number_p = 't') then
      num := self.num;
      if (num > 1e127d) then
        return '1e309'; -- json representation of infinity !?
      end if;
      if (num < -1e127d) then
        return '-1e309'; -- json representation of infinity !?
      end if;
      buf := STANDARD.to_char(num, 'TM9', 'NLS_NUMERIC_CHARACTERS=''.,''');
      if (-1 < num and num < 0 and substr(buf, 1, 2) = '-.') then
        buf := '-0' || substr(buf, 2);
      elsif (0 < num and num < 1 and substr(buf, 1, 1) = '.') then
        buf := '0' || buf;
      end if;
      return buf;
    else
      num_double := self.num_double;
      if (num_double = +BINARY_DOUBLE_INFINITY) then
        return '1e309'; -- json representation of infinity !?
      end if;
      if (num_double = -BINARY_DOUBLE_INFINITY) then
        return '-1e309'; -- json representation of infinity !?
      end if;
      buf := STANDARD.to_char(num_double, 'TM9', 'NLS_NUMERIC_CHARACTERS=''.,''');
      if (-1 < num_double and num_double < 0 and substr(buf, 1, 2) = '-.') then
        buf := '-0' || substr(buf, 2);
      elsif (0 < num_double and num_double < 1 and substr(buf, 1, 1) = '.') then
        buf := '0' || buf;
      end if;
      return buf;
    end if;
  end number_toString;
end;
/
show err

-- --- pljson_bool.type (spec + body) ---
create or replace type pljson_bool force under pljson_element (
  
  num number(1),
  
  constructor function pljson_bool (b in boolean) return self as result,
  overriding member function is_bool return boolean,
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2,
  
  overriding member function get_bool return boolean
) not final
/
show err

create or replace type body pljson_bool as
  
  constructor function pljson_bool (b in boolean) return self as result as
  begin
    self.typeval := 5;
    self.num := 0;
    if b then self.num := 1; end if;
    return;
  end;
  
  overriding member function is_bool return boolean as
  begin
    return true;
  end;
  
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    if self.num = 1 then return 'true'; else return 'false'; end if;
  end;
  
  overriding member function get_bool return boolean as
  begin
    return self.num = 1;
  end;
end;
/
show err

-- --- pljson_null.type (spec + body) ---
create or replace type pljson_null force under pljson_element
(
  constructor function pljson_null return self as result,
  overriding member function is_null return boolean,
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2
) not final
/
show err

create or replace type body pljson_null as
  
  constructor function pljson_null return self as result as
  begin
    self.typeval := 6;
    return;
  end;
  
  overriding member function is_null return boolean as
  begin
    return true;
  end;
  
  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    return 'null';
  end;
end;
/
show err

-- ====================================================================
-- STEP 3: Creare package specs
-- ====================================================================
PROMPT [3/6] Creare package specifications...;


-- --- pljson_ext.decl ---
create or replace package pljson_ext as
  /*
  Copyright (c) 2009 Jonas Krogsboell

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */

  /* This package contains extra methods to lookup types and
     an easy way of adding date values in json - without changing the structure */
  function parsePath(json_path varchar2, base number default 1) return pljson_list;

  --JSON pre-parsed path getters
  function get_json_element(obj pljson, path pljson_list) return pljson_element;
  function get_string(obj pljson, path pljson_list) return varchar2;
  function get_number(obj pljson, path pljson_list) return number;
  function get_double(obj pljson, path pljson_list) return binary_double;
  function get_json(obj pljson, path pljson_list) return pljson;
  function get_json_list(obj pljson, path pljson_list) return pljson_list;
  function get_bool(obj pljson, path pljson_list) return boolean;
  function get_date(obj pljson, path pljson_list) return date;

  --saved original code, in case of future bug troubleshooting
  function get_json_element_original(obj pljson, v_path varchar2, base number default 1) return pljson_element;
  
  --JSON Path getters
  function get_json_element(obj pljson, v_path varchar2, base number default 1) return pljson_element;
  function get_string(obj pljson, path varchar2,       base number default 1) return varchar2;
  function get_number(obj pljson, path varchar2,       base number default 1) return number;
  function get_double(obj pljson, path varchar2,       base number default 1) return binary_double;
  function get_json(obj pljson, path varchar2,         base number default 1) return pljson;
  function get_json_list(obj pljson, path varchar2,    base number default 1) return pljson_list;
  function get_bool(obj pljson, path varchar2,         base number default 1) return boolean;

  --JSON pre-parsed path putters
  procedure put(obj in out nocopy pljson, path pljson_list, elem varchar2);
  procedure put(obj in out nocopy pljson, path pljson_list, elem number);
  procedure put(obj in out nocopy pljson, path pljson_list, elem binary_double);
  procedure put(obj in out nocopy pljson, path pljson_list, elem pljson);
  procedure put(obj in out nocopy pljson, path pljson_list, elem pljson_list);
  procedure put(obj in out nocopy pljson, path pljson_list, elem boolean);
  procedure put(obj in out nocopy pljson, path pljson_list, elem pljson_element);
  procedure put(obj in out nocopy pljson, path pljson_list, elem date);

  --JSON Path putters
  procedure put(obj in out nocopy pljson, path varchar2, elem varchar2,   base number default 1);
  procedure put(obj in out nocopy pljson, path varchar2, elem number,     base number default 1);
  procedure put(obj in out nocopy pljson, path varchar2, elem binary_double, base number default 1);
  procedure put(obj in out nocopy pljson, path varchar2, elem pljson,       base number default 1);
  procedure put(obj in out nocopy pljson, path varchar2, elem pljson_list,  base number default 1);
  procedure put(obj in out nocopy pljson, path varchar2, elem boolean,    base number default 1);
  procedure put(obj in out nocopy pljson, path varchar2, elem pljson_element, base number default 1);

  procedure remove(obj in out nocopy pljson, path pljson_list);
  procedure remove(obj in out nocopy pljson, path varchar2, base number default 1);

  --Pretty print with JSON Path - obsolete in 0.9.4 - obj.path(v_path).(to_char,print,htp)
  function pp(obj pljson, v_path varchar2) return varchar2;
  procedure pp(obj pljson, v_path varchar2); --using dbms_output.put_line
  procedure pp_htp(obj pljson, v_path varchar2); --using htp.print

  --extra function checks if number has no fraction
  function is_integer(v pljson_element) return boolean;

  format_string varchar2(30 char) := 'yyyy-mm-dd hh24:mi:ss';
  --extension enables json to store dates without compromising the implementation
  function to_json_string(d date) return pljson_string;
  --notice that a date type in json is also a varchar2
  function is_date(v pljson_element) return boolean;
  --conversion is needed to extract dates
  function to_date(v pljson_element) return date;
  -- alias so that old code doesn't break
  function to_date2(v pljson_element) return date;
  --JSON Path with date
  function get_date(obj pljson, path varchar2, base number default 1) return date;
  procedure put(obj in out nocopy pljson, path varchar2, elem date, base number default 1);

  /*
    encoding in lines of 64 chars ending with CR+NL
  */
  function encodeBase64Blob2Clob(p_blob in  blob) return clob;
  /*
    assumes single base64 string or broken into equal length lines of max 64 or 76 chars
    (as specified by RFC-1421 or RFC-2045)
    line ending can be CR+NL or NL
  */
  function decodeBase64Clob2Blob(p_clob clob) return blob;

  function base64(binarydata blob) return pljson_list;
  function base64(l pljson_list) return blob;

  function encode(binarydata blob) return pljson_string;
  function decode(v pljson_string) return blob;

  /*
    implemented as a procedure to force you to declare the CLOB so you can free it later
  */
  procedure blob2clob(b blob, c out clob, charset varchar2 default 'UTF8');
end pljson_ext;
/
show err

-- --- pljson_parser.decl ---
create or replace package pljson_parser as
  /*
  Copyright (c) 2010 Jonas Krogsboell

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */

  /** Internal type for processing. */
  /* scanner tokens:
    '{', '}', ',', ':', '[', ']', STRING, NUMBER, TRUE, FALSE, NULL
  */
  type rToken IS RECORD (
    type_name VARCHAR2(7),
    line PLS_INTEGER,
    col PLS_INTEGER,
    data VARCHAR2(32767),
    data_overflow clob); -- max_string_size

  type lTokens is table of rToken index by pls_integer;
  type json_src is record (len number, offset number, offset_chars number, src varchar2(32767), s_clob clob, src_len number, src_array pljson_varray);

  json_strict boolean not null := false;
  empty_string_as_null boolean not null := false;

  ucs2_exception EXCEPTION;
  pragma exception_init(ucs2_exception, -22831);

  procedure set_buffer_amount(amount pls_integer);

  function lengthcc(buf clob) return number;

  function next_char(indx number, s in out nocopy json_src) return varchar2;
  function next_char2(indx number, s in out nocopy json_src, amount number default 1) return varchar2;
  function parseObj(tokens lTokens, indx in out nocopy pls_integer) return pljson;

  function prepareClob(buf in clob) return pljson_parser.json_src;
  function prepareVarchar2(buf in varchar2) return pljson_parser.json_src;
  function lexer(jsrc in out nocopy json_src) return lTokens;
  procedure print_token(t rToken);

  /**
   * <p>Primary parsing method. It can parse a JSON object.</p>
   *
   * @return An instance of <code>pljson</code>.
   * @throws PARSER_ERROR -20101 when invalid input found.
   * @throws SCANNER_ERROR -20100 when lexing fails.
   */
  function parser(str varchar2) return pljson;
  function parse_list(str varchar2) return pljson_list;
  function parse_any(str varchar2) return pljson_element;
  function parser(str clob) return pljson;
  function parse_list(str clob) return pljson_list;
  function parse_any(str clob) return pljson_element;
  procedure remove_duplicates(obj in out nocopy pljson);
  function get_version return varchar2;

end pljson_parser;
/
show err


-- ====================================================================
-- STEP 4: Creare package bodies si type implementations
-- ====================================================================
PROMPT [4/6] Creare package bodies si type implementations...;


-- --- pljson_parser.impl (body) ---
create or replace package body pljson_parser as
  /*
  Copyright (c) 2009 Jonas Krogsboell

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */

  decimalpoint varchar2(1 char) := '.';
  -- 1. perfomance optimal value (such as any of 200 - 500) for length of part s_clob in set_src_array
  -- 2. 4000 byte limitation in select "substr(s.src, level, 1)" in set_src_array
  buffer_amount number := 200;

  procedure set_buffer_amount(amount pls_integer) as
  begin
    if amount > 0 and amount <= 4000 then
      buffer_amount := amount;
    end if;
  end;

  procedure set_src_array(s in out nocopy json_src) as
  begin
    -- calc length src one time after subtsr clob
    s.src_len := length(s.src);
    -- get collection of char - faster than substr in loop in pl\sql
    -- it is necessary to remember about the limitation varchar2 - 4000 byte
    select substr(s.src, level, 1)
      bulk collect into s.src_array
      from dual
      connect by level <= s.src_len;
  end;

  function lengthcc(buf clob) return number as
   offset number := 0;
   len number := 0;
   src varchar2(32767);
  begin
    while true loop
      begin
        src := dbms_lob.substr(buf, buffer_amount, offset+1);
      exception
      when ucs2_exception then
        src := dbms_lob.substr(buf, buffer_amount-1, offset+1);
      end;
      exit when src is null;
      len := len + length(src);
      offset := offset + length2(src);
      --dbms_output.put_line('offset = ' || offset || ' len = ' || len);
    end loop;
    return len;
  end;

  procedure update_decimalpoint as
  begin
    select substr(value, 1, 1)
    into decimalpoint
    from nls_session_parameters
    where parameter = 'NLS_NUMERIC_CHARACTERS';
  end update_decimalpoint;

  /* type json_src is record (len number, offset number, src varchar2(32767), s_clob clob); */
  /* assertions
    offset: contains 0-base offset of buffer,
      so 1-st entry is offset + 1, 4000-th entry = offset + 4000
    src: contains offset + 1 .. offset + 4000, ex. 1..4000, 4001..8000, etc.
  */
  function next_char(indx number, s in out nocopy json_src) return varchar2 as
  begin
    if (indx > s.len) then return null; end if;

    --right offset?
    /* if (indx > 4000 + s.offset or indx < s.offset) then */
    /* fix for issue #37 */
    /* code before fix for issue #169
    if (indx > 4000 + s.offset or indx <= s.offset) then
      s.offset := indx - (indx mod 4000);
      -- addon fix for issue #37
      if s.offset = indx then
        s.offset := s.offset - 4000;
      end if;
      s.src := dbms_lob.substr(s.s_clob, 4000, s.offset+1);
    end if;
    --read from s.src
    return substr(s.src, indx-s.offset, 1);
    */

    /* use of length, so works correctly for 4-byte unicode characters (issue #169) */
    /* lengthc does not work (issue #190) */
    if (indx > s.src_len + s.offset_chars) then
      while (indx > length(s.src) + s.offset_chars) loop
        s.offset_chars := s.offset_chars + length(s.src);
        s.offset := s.offset + length2(s.src);
        /* exception check, so works correctly for 4-byte unicode characters (issue #169) */
        begin
          s.src := dbms_lob.substr(s.s_clob, buffer_amount, s.offset+1);
        exception
        when ucs2_exception then
          s.src := dbms_lob.substr(s.s_clob, buffer_amount-1, s.offset+1);
        end;
        set_src_array(s);
      end loop;
    elsif (indx <= s.offset_chars) then
      s.offset_chars := 0;
      s.offset := 0;
      /* exception check, so works correctly for 4-byte unicode characters (issue #169) */
      begin
        s.src := dbms_lob.substr(s.s_clob, buffer_amount, s.offset+1);
      exception
      when ucs2_exception then
        s.src := dbms_lob.substr(s.s_clob, buffer_amount-1, s.offset+1);
      end;
      set_src_array(s);
      while (indx > length(s.src) + s.offset_chars) loop
        s.offset_chars := s.offset_chars + length(s.src);
        s.offset := s.offset + length2(s.src);
        /* exception check, so works correctly for 4-byte unicode characters (issue #169) */
        begin
          s.src := dbms_lob.substr(s.s_clob, buffer_amount, s.offset+1);
        exception
        when ucs2_exception then
          s.src := dbms_lob.substr(s.s_clob, buffer_amount-1, s.offset+1);
        end;
        set_src_array(s);
      end loop;
    end if;
    --dbms_output.put_line('indx: ' || indx || ' offset: ' || s.offset || ' (chars: ' || s.offset_chars || ') src chars: ' || length(s.src));
    return s.src_array(indx-s.offset_chars);
  end;

  function next_char2(indx number, s in out nocopy json_src, amount number default 1) return varchar2 as
    buf varchar2(32767) := '';
  begin
    for i in 1..amount loop
      buf := buf || next_char(indx-1+i, s);
    end loop;
    return buf;
  end;

  function prepareClob(buf clob) return pljson_parser.json_src as
    temp pljson_parser.json_src;
  begin
    temp.s_clob := buf;
    temp.offset_chars := 0;
    temp.offset := 0;
    /* exception check, so works correctly for 4-byte unicode characters (issue #169) */
    begin
      temp.src := dbms_lob.substr(buf, buffer_amount, temp.offset+1);
    exception
    when ucs2_exception then
      temp.src := dbms_lob.substr(buf, buffer_amount-1, temp.offset+1);
    end;
    /* use of lengthcc, so works correctly for 4-byte unicode characters (issue #169) */
    temp.len := lengthcc(buf); --dbms_lob.getlength(buf);
    set_src_array(temp);
    return temp;
  end;

  function prepareVarchar2(buf varchar2) return pljson_parser.json_src as
    temp pljson_parser.json_src;
  begin
    temp.s_clob := buf;
    temp.offset_chars := 0;
    temp.offset := 0;
    temp.src := substr(buf, 1, buffer_amount);
    temp.len := length(buf);
    set_src_array(temp);
    return temp;
  end;

  procedure debug(text varchar2) as
  begin
    dbms_output.put_line(text);
  end;

  procedure print_token(t rToken) as
  begin
    dbms_output.put_line('Line: '||t.line||' - Column: '||t.col||' - Type: '||t.type_name||' - Content: '||t.data);
  end print_token;

  /* SCANNER FUNCTIONS START */
  procedure s_error(text varchar2, line number, col number) as
  begin
    raise_application_error(-20100, 'JSON Scanner exception @ line: '||line||' column: '||col||' - '||text);
  end;

  procedure s_error(text varchar2, tok rToken) as
  begin
    raise_application_error(-20100, 'JSON Scanner exception @ line: '||tok.line||' column: '||tok.col||' - '||text);
  end;

  function mt(t varchar2, l pls_integer, c pls_integer, d varchar2) return rToken as
    token rToken;
  begin
    token.type_name := t;
    token.line := l;
    token.col := c;
    token.data := d;
    return token;
  end;

  function lexNumber(jsrc in out nocopy json_src, tok in out nocopy rToken, indx in out nocopy pls_integer) return pls_integer as
    numbuf varchar2(4000) := '';
    buf varchar2(4);
    checkLoop boolean;
  begin
    buf := next_char(indx, jsrc);
    if (buf = '-') then numbuf := '-'; indx := indx + 1; end if;
    buf := next_char(indx, jsrc);
    --0 or [1-9]([0-9])*
    if (buf = '0') then
      numbuf := numbuf || '0'; indx := indx + 1;
      buf := next_char(indx, jsrc);
    elsif (buf >= '1' and buf <= '9') then
      numbuf := numbuf || buf; indx := indx + 1;
      --read digits
      buf := next_char(indx, jsrc);
      while (buf >= '0' and buf <= '9') loop
        numbuf := numbuf || buf; indx := indx + 1;
        buf := next_char(indx, jsrc);
      end loop;
    end if;
    --fraction
    if (buf = '.') then
      numbuf := numbuf || buf; indx := indx + 1;
      buf := next_char(indx, jsrc);
      checkLoop := FALSE;
      while (buf >= '0' and buf <= '9') loop
        checkLoop := TRUE;
        numbuf := numbuf || buf; indx := indx + 1;
        buf := next_char(indx, jsrc);
      end loop;
      if (not checkLoop) then
        s_error('Expected: digits in fraction', tok);
      end if;
    end if;
    --exp part
    if (buf in ('e', 'E')) then
      numbuf := numbuf || buf; indx := indx + 1;
      buf := next_char(indx, jsrc);
      if (buf = '+' or buf = '-') then
        numbuf := numbuf || buf; indx := indx + 1;
        buf := next_char(indx, jsrc);
      end if;
      checkLoop := FALSE;
      while (buf >= '0' and buf <= '9') loop
        checkLoop := TRUE;
        numbuf := numbuf || buf; indx := indx + 1;
        buf := next_char(indx, jsrc);
      end loop;
      if (not checkLoop) then
        s_error('Expected: digits in exp', tok);
      end if;
    end if;

    tok.data := numbuf;
    return indx;
  end lexNumber;

  -- [a-zA-Z]([a-zA-Z0-9])*
  function lexName(jsrc in out nocopy json_src, tok in out nocopy rToken, indx in out nocopy pls_integer) return pls_integer as
    varbuf varchar2(32767) := '';
    buf varchar(4);
    num number;
  begin
    buf := next_char(indx, jsrc);
    while (REGEXP_LIKE(buf, '^[[:alnum:]\_]$', 'i')) loop
      varbuf := varbuf || buf;
      indx := indx + 1;
      buf := next_char(indx, jsrc);
      if (buf is null) then
        goto retname;
        --debug('Premature string ending');
      end if;
    end loop;
    <<retname>>
    --could check for reserved keywords here
    --debug(varbuf);
    tok.data := varbuf;
    return indx-1;
  end lexName;

  procedure updateClob(v_extended in out nocopy clob, v_str varchar2) as
  begin
    /* use of length2, so works correctly for 4-byte unicode characters (issue #169) */
    dbms_lob.writeappend(v_extended, length2(v_str), v_str);
  end updateClob;

  function lexString(jsrc in out nocopy json_src, tok in out nocopy rToken, indx in out nocopy pls_integer, endChar char) return pls_integer as
    v_extended clob := null; v_count number := 0;
    varbuf varchar2(32767) := '';
    buf varchar(4);
    wrong boolean;
    max_string_chars number := 5000; /* chunk size, less than this number may be copied */
  begin
    indx := indx + 1;
    buf := next_char(indx, jsrc);
    while (buf != endChar) loop
      --clob control
      if (v_count > 8191) then --crazy oracle error (16383 is the highest working length with unistr - 8192 choosen to be safe)
        if (v_extended is null) then
          v_extended := empty_clob();
          dbms_lob.createtemporary(v_extended, true);
        end if;
        updateClob(v_extended, unistr(varbuf));
        varbuf := ''; v_count := 0;
      end if;
      if (buf = Chr(13) or buf = CHR(9) or buf = CHR(10)) then
        s_error('Control characters not allowed (CHR(9),CHR(10),CHR(13))', tok);
      end if;
      if (buf = '\') then
        --varbuf := varbuf || buf;
        indx := indx + 1;
        buf := next_char(indx, jsrc);
        case
          when buf in ('\') then
            varbuf := varbuf || buf || buf; v_count := v_count + 2;
            indx := indx + 1;
            buf := next_char(indx, jsrc);
          when buf in ('"', '/') then
            varbuf := varbuf || buf; v_count := v_count + 1;
            indx := indx + 1;
            buf := next_char(indx, jsrc);
          when buf = '''' then
            if (json_strict = false) then
              varbuf := varbuf || buf; v_count := v_count + 1;
              indx := indx + 1;
              buf := next_char(indx, jsrc);
            else
              s_error('strictmode - expected: " \ / b f n r t u ', tok);
            end if;
          when buf in ('b', 'f', 'n', 'r', 't') then
            --backspace b = U+0008
            --formfeed  f = U+000C
            --newline   n = U+000A
            --carret    r = U+000D
            --tabulator t = U+0009
            case buf
            when 'b' then varbuf := varbuf || chr(8);
            when 'f' then varbuf := varbuf || chr(12);
            when 'n' then varbuf := varbuf || chr(10);
            when 'r' then varbuf := varbuf || chr(13);
            when 't' then varbuf := varbuf || chr(9);
            end case;
            --varbuf := varbuf || buf;
            v_count := v_count + 1;
            indx := indx + 1;
            buf := next_char(indx, jsrc);
          when buf = 'u' then
            --four hexadecimal chars
            declare
              four varchar2(4);
            begin
              four := next_char2(indx+1, jsrc, 4);
              wrong := FALSE;
              if (upper(substr(four, 1, 1)) not in ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','a','b','c','d','e','f')) then wrong := TRUE; end if;
              if (upper(substr(four, 2, 1)) not in ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','a','b','c','d','e','f')) then wrong := TRUE; end if;
              if (upper(substr(four, 3, 1)) not in ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','a','b','c','d','e','f')) then wrong := TRUE; end if;
              if (upper(substr(four, 4, 1)) not in ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','a','b','c','d','e','f')) then wrong := TRUE; end if;
              if (wrong) then
                s_error('expected: " \u([0-9][A-F]){4}', tok);
              end if;
--              varbuf := varbuf || buf || four;
              varbuf := varbuf || '\'||four;--chr(to_number(four,'XXXX'));
              v_count := v_count + 5;
              indx := indx + 5;
              buf := next_char(indx, jsrc);
              end;
          else
            s_error('expected: " \ / b f n r t u ', tok);
        end case;
      else
        varbuf := varbuf || buf; v_count := v_count + 1;
        indx := indx + 1;
        buf := next_char(indx, jsrc);
      end if;
    end loop;

    if (buf is null) then
      s_error('string ending not found', tok);
      --debug('Premature string ending');
    end if;

    --debug(varbuf);
    --dbms_output.put_line(varbuf);
    if (v_extended is not null) then
      updateClob(v_extended, unistr(varbuf));
      tok.data_overflow := v_extended;
      -- tok.data := dbms_lob.substr(v_extended, 1, 32767);
      /* may read less than "max_string_chars" characters but it's a sample so doesn't matter */
      dbms_lob.read(v_extended, max_string_chars, 1, tok.data);
    else
      tok.data := unistr(varbuf);
    end if;
    return indx;
  end lexString;

  /* scanner tokens:
    '{', '}', ',', ':', '[', ']', STRING, NUMBER, TRUE, FALSE, NULL
  */
  function lexer(jsrc in out nocopy json_src) return lTokens as
    tokens lTokens;
    indx pls_integer := 1;
    tok_indx pls_integer := 1;
    buf varchar2(4);
    lin_no number := 1;
    col_no number := 0;
  begin
    while (indx <= jsrc.len) loop
      --read into buf
      buf := next_char(indx, jsrc);
      col_no := col_no + 1;
      --convert to switch case
      case
        when buf = '{' then tokens(tok_indx) := mt('{', lin_no, col_no, null); tok_indx := tok_indx + 1;
        when buf = '}' then tokens(tok_indx) := mt('}', lin_no, col_no, null); tok_indx := tok_indx + 1;
        when buf = ',' then tokens(tok_indx) := mt(',', lin_no, col_no, null); tok_indx := tok_indx + 1;
        when buf = ':' then tokens(tok_indx) := mt(':', lin_no, col_no, null); tok_indx := tok_indx + 1;
        when buf = '[' then tokens(tok_indx) := mt('[', lin_no, col_no, null); tok_indx := tok_indx + 1;
        when buf = ']' then tokens(tok_indx) := mt(']', lin_no, col_no, null); tok_indx := tok_indx + 1;
        when buf = 't' then
          if (next_char2(indx, jsrc, 4) != 'true') then
            if (json_strict = false and REGEXP_LIKE(buf, '^[[:alpha:]]$', 'i')) then
              tokens(tok_indx) := mt('STRING', lin_no, col_no, null);
              indx := lexName(jsrc, tokens(tok_indx), indx);
              col_no := col_no + length(tokens(tok_indx).data) + 1;
              tok_indx := tok_indx + 1;
            else
              s_error('Expected: ''true''', lin_no, col_no);
            end if;
          else
            tokens(tok_indx) := mt('TRUE', lin_no, col_no, null); tok_indx := tok_indx + 1;
            indx := indx + 3;
            col_no := col_no + 3;
          end if;
        when buf = 'n' then
          if (next_char2(indx, jsrc, 4) != 'null') then
            if (json_strict = false and REGEXP_LIKE(buf, '^[[:alpha:]]$', 'i')) then
              tokens(tok_indx) := mt('STRING', lin_no, col_no, null);
              indx := lexName(jsrc, tokens(tok_indx), indx);
              col_no := col_no + length(tokens(tok_indx).data) + 1;
              tok_indx := tok_indx + 1;
            else
              s_error('Expected: ''null''', lin_no, col_no);
            end if;
          else
            tokens(tok_indx) := mt('NULL', lin_no, col_no, null); tok_indx := tok_indx + 1;
            indx := indx + 3;
            col_no := col_no + 3;
          end if;
        when buf = 'f' then
          if (next_char2(indx, jsrc, 5) != 'false') then
            if (json_strict = false and REGEXP_LIKE(buf, '^[[:alpha:]]$', 'i')) then
              tokens(tok_indx) := mt('STRING', lin_no, col_no, null);
              indx := lexName(jsrc, tokens(tok_indx), indx);
              col_no := col_no + length(tokens(tok_indx).data) + 1;
              tok_indx := tok_indx + 1;
            else
              s_error('Expected: ''false''', lin_no, col_no);
            end if;
          else
            tokens(tok_indx) := mt('FALSE', lin_no, col_no, null); tok_indx := tok_indx + 1;
            indx := indx + 4;
            col_no := col_no + 4;
          end if;
        /* -- 9 = TAB, 10 = \n, 13 = \r (Linux = \n, Windows = \r\n, Mac = \r */
        when (buf = Chr(10)) then --linux newlines
          lin_no := lin_no + 1;
          col_no := 0;

        when (buf = Chr(13)) then --Windows or Mac way
          lin_no := lin_no + 1;
          col_no := 0;
          if (jsrc.len >= indx+1) then -- better safe than sorry
            buf := next_char(indx+1, jsrc);
            if (buf = Chr(10)) then --\r\n
              indx := indx + 1;
            end if;
          end if;

        when (buf = CHR(9)) then null; --tabbing
        when (buf in ('-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) then --number
          tokens(tok_indx) := mt('NUMBER', lin_no, col_no, null);
          indx := lexNumber(jsrc, tokens(tok_indx), indx)-1;
          col_no := col_no + length(tokens(tok_indx).data);
          tok_indx := tok_indx + 1;
        when buf = '"' then --string
          tokens(tok_indx) := mt('STRING', lin_no, col_no, null);
          indx := lexString(jsrc, tokens(tok_indx), indx, '"');
          col_no := col_no + length(tokens(tok_indx).data) + 1;
          tok_indx := tok_indx + 1;
        when buf = '''' and json_strict = false then --string
          tokens(tok_indx) := mt('STRING', lin_no, col_no, null);
          indx := lexString(jsrc, tokens(tok_indx), indx, '''');
          col_no := col_no + length(tokens(tok_indx).data) + 1; --hovsa her
          tok_indx := tok_indx + 1;
        when json_strict = false and REGEXP_LIKE(buf, '^[[:alpha:]]$', 'i') then
          tokens(tok_indx) := mt('STRING', lin_no, col_no, null);
          indx := lexName(jsrc, tokens(tok_indx), indx);
          if (tokens(tok_indx).data_overflow is not null) then
            /* use of lengthcc, so works correctly for 4-byte unicode characters (issue #169) */
            col_no := col_no + lengthcc(tokens(tok_indx).data_overflow) + 1; --dbms_lob.getlength(tokens(tok_indx).data_overflow) + 1;
          else
            col_no := col_no + length(tokens(tok_indx).data) + 1;
          end if;
          tok_indx := tok_indx + 1;
        when json_strict = false and buf||next_char(indx+1, jsrc) = '/*' then --strip comments
          declare
            saveindx number := indx;
            un_esc clob;
          begin
            indx := indx + 1;
            loop
              indx := indx + 1;
              buf := next_char(indx, jsrc)||next_char(indx+1, jsrc);
              exit when buf = '*/';
              exit when buf is null;
            end loop;

            if (indx = saveindx+2) then
              --enter unescaped mode
              --dbms_output.put_line('Entering unescaped mode');
              un_esc := empty_clob();
              dbms_lob.createtemporary(un_esc, true);
              indx := indx + 1;
              loop
                indx := indx + 1;
                buf := next_char(indx, jsrc)||next_char(indx+1, jsrc)||next_char(indx+2, jsrc)||next_char(indx+3, jsrc);
                exit when buf = '/**/';
                if buf is null then
                  s_error('Unexpected sequence /**/ to end unescaped data: '||buf, lin_no, col_no);
                end if;
                buf := next_char(indx, jsrc);
                /* use of length2, so works correctly for 4-byte unicode characters (issue #169) */
                dbms_lob.writeappend(un_esc, length2(buf), buf);
              end loop;
              tokens(tok_indx) := mt('ESTRING', lin_no, col_no, null);
              tokens(tok_indx).data_overflow := un_esc;
              /* use of lengthcc, so works correctly for 4-byte unicode characters (issue #169) */
              col_no := col_no + lengthcc(un_esc) + 1; --dbms_lob.getlength(un_esc) + 1; --note: line count won't work properly
              tok_indx := tok_indx + 1;
              indx := indx + 2;
            end if;

            indx := indx + 1;
          end;
        when buf = ' ' then null; --space
        else
          s_error('Unexpected char: '||buf, lin_no, col_no);
      end case;

      indx := indx + 1;
    end loop;

    return tokens;
  end lexer;

  /* SCANNER END */

  /* PARSER FUNCTIONS START */
  procedure p_error(text varchar2, tok rToken) as
  begin
    raise_application_error(-20101, 'JSON Parser exception @ line: '||tok.line||' column: '||tok.col||' - '||text);
  end;

  function parseArr(tokens lTokens, indx in out nocopy pls_integer) return pljson_list as
    e_arr pljson_element_array := pljson_element_array();
    ret_list pljson_list := pljson_list();
    v_count number := 0;
    tok rToken;
    pv pljson_number;
  begin
    --value, value, value ]
    if (indx > tokens.count) then p_error('more elements in array was excepted', tok); end if;
    tok := tokens(indx);
    while (tok.type_name != ']') loop
      e_arr.extend;
      v_count := v_count + 1;
      case tok.type_name
        when 'TRUE' then e_arr(v_count) := pljson_bool(true);
        when 'FALSE' then e_arr(v_count) := pljson_bool(false);
        when 'NULL' then e_arr(v_count) := pljson_null();
        when 'STRING' then e_arr(v_count) := case when tok.data_overflow is not null then pljson_string(tok.data_overflow) else pljson_string(tok.data) end;
        when 'ESTRING' then e_arr(v_count) := pljson_string(tok.data_overflow, false);
        /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
        --when 'NUMBER' then e_arr(v_count) := pljson_number(to_number(replace(tok.data, '.', decimalpoint)));
        when 'NUMBER' then
          pv := pljson_number(0);
          pv.parse_number(replace(tok.data, '.', decimalpoint));
          e_arr(v_count) := pv;
        when '[' then
          declare e_list pljson_list; begin
            indx := indx + 1;
            e_list := parseArr(tokens, indx);
            e_arr(v_count) := e_list;
          end;
        when '{' then
          indx := indx + 1;
          e_arr(v_count) := parseObj(tokens, indx);
        else
          p_error('Expected a value', tok);
      end case;
      indx := indx + 1;
      if (indx > tokens.count) then p_error('] not found', tok); end if;
      tok := tokens(indx);
      if (tok.type_name = ',') then --advance
        indx := indx + 1;
        if (indx > tokens.count) then p_error('more elements in array was excepted', tok); end if;
        tok := tokens(indx);
        if (tok.type_name = ']') then --premature exit
          p_error('Premature exit in array', tok);
        end if;
      elsif (tok.type_name != ']') then --error
        p_error('Expected , or ]', tok);
      end if;

    end loop;
    ret_list.list_data := e_arr;
    return ret_list;
  end parseArr;

  function parseMem(tokens lTokens, indx in out pls_integer, mem_name varchar2, mem_indx number) return pljson_element as
    mem pljson_element;
    tok rToken;
    pv pljson_number;
  begin
    tok := tokens(indx);
    case tok.type_name
      when 'TRUE' then mem := pljson_bool(true);
      when 'FALSE' then mem := pljson_bool(false);
      when 'NULL' then mem := pljson_null();
      when 'STRING' then mem := case when tok.data_overflow is not null then pljson_string(tok.data_overflow) else pljson_string(tok.data) end;
      when 'ESTRING' then mem := pljson_string(tok.data_overflow, false);
      /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
      --when 'NUMBER' then mem := pljson_number(to_number(replace(tok.data, '.', decimalpoint)));
      when 'NUMBER' then
        pv := pljson_number(0);
        pv.parse_number(replace(tok.data, '.', decimalpoint));
        mem := pv;
      when '[' then
        declare
          e_list pljson_list;
        begin
          indx := indx + 1;
          e_list := parseArr(tokens, indx);
          mem := e_list;
        end;
      when '{' then
        indx := indx + 1;
        mem := parseObj(tokens, indx);
      else
        p_error('Found '||tok.type_name, tok);
    end case;
    mem.mapname := mem_name;
    mem.mapindx := mem_indx;

    indx := indx + 1;
    return mem;
  end parseMem;

  /*procedure test_duplicate_members(arr in json_member_array, mem_name in varchar2, wheretok rToken) as
  begin
    for i in 1 .. arr.count loop
      if (arr(i).member_name = mem_name) then
        p_error('Duplicate member name', wheretok);
      end if;
    end loop;
  end test_duplicate_members;*/

  function parseObj(tokens lTokens, indx in out nocopy pls_integer) return pljson as
    type memmap is table of number index by varchar2(4000); -- i've read somewhere that this is not possible - but it is!
    mymap memmap;
    nullelemfound boolean := false;

    obj pljson;
    tok rToken;
    mem_name varchar(4000);
    arr pljson_element_array := pljson_element_array();
  begin
    --what to expect?
    while (indx <= tokens.count) loop
      tok := tokens(indx);
      --debug('E: '||tok.type_name);
      case tok.type_name
      when 'STRING' then
        --member
        mem_name := substr(tok.data, 1, 4000);
        begin
          if (mem_name is null) then
            if (nullelemfound) then
              p_error('Duplicate empty member: ', tok);
            else
              nullelemfound := true;
            end if;
          elsif (mymap(mem_name) is not null) then
            p_error('Duplicate member name: '||mem_name, tok);
          end if;
        exception
          when no_data_found then mymap(mem_name) := 1;
        end;

        indx := indx + 1;
        if (indx > tokens.count) then p_error('Unexpected end of input', tok); end if;
        tok := tokens(indx);
        indx := indx + 1;
        if (indx > tokens.count) then p_error('Unexpected end of input', tok); end if;
        if (tok.type_name = ':') then
          --parse
          declare
            jmb pljson_element;
            x number;
          begin
            x := arr.count + 1;
            jmb := parseMem(tokens, indx, mem_name, x);
            arr.extend;
            arr(x) := jmb;
          end;
        else
          p_error('Expected '':''', tok);
        end if;
        --move indx forward if ',' is found
        if (indx > tokens.count) then p_error('Unexpected end of input', tok); end if;

        tok := tokens(indx);
        if (tok.type_name = ',') then
          --debug('found ,');
          indx := indx + 1;
          tok := tokens(indx);
          if (tok.type_name = '}') then --premature exit
            p_error('Premature exit in json object', tok);
          end if;
        elsif (tok.type_name != '}') then
           p_error('A comma seperator is probably missing', tok);
        end if;
      when '}' then
        obj := pljson();
        obj.json_data := arr;
        return obj;
      else
        p_error('Expected string or }', tok);
      end case;
    end loop;

    p_error('} not found', tokens(indx-1));

    return obj;

  end;

  function parser(str varchar2) return pljson as
    tokens lTokens;
    obj pljson;
    indx pls_integer := 1;
    jsrc json_src;
  begin
    --update_decimalpoint();
    jsrc := prepareVarchar2(str);
    tokens := lexer(jsrc);
    if (tokens(indx).type_name = '{') then
      indx := indx + 1;
      obj := parseObj(tokens, indx);
    else
      raise_application_error(-20101, 'JSON Parser exception - no { start found');
    end if;
    if (tokens.count != indx) then
      p_error('} should end the JSON object', tokens(indx));
    end if;

    return obj;
  end parser;

  function parse_list(str varchar2) return pljson_list as
    tokens lTokens;
    obj pljson_list;
    indx pls_integer := 1;
    jsrc json_src;
  begin
    --update_decimalpoint();
    jsrc := prepareVarchar2(str);
    tokens := lexer(jsrc);
    if (tokens(indx).type_name = '[') then
      indx := indx + 1;
      obj := parseArr(tokens, indx);
    else
      raise_application_error(-20101, 'JSON List Parser exception - no [ start found');
    end if;
    if (tokens.count != indx) then
      p_error('] should end the JSON List object', tokens(indx));
    end if;

    return obj;
  end parse_list;

  function parse_list(str clob) return pljson_list as
    tokens lTokens;
    obj pljson_list;
    indx pls_integer := 1;
    jsrc json_src;
  begin
    --update_decimalpoint();
    jsrc := prepareClob(str);
    tokens := lexer(jsrc);
    if (tokens(indx).type_name = '[') then
      indx := indx + 1;
      obj := parseArr(tokens, indx);
    else
      raise_application_error(-20101, 'JSON List Parser exception - no [ start found');
    end if;
    if (tokens.count != indx) then
      p_error('] should end the JSON List object', tokens(indx));
    end if;

    return obj;
  end parse_list;

  function parser(str clob) return pljson as
    tokens lTokens;
    obj pljson;
    indx pls_integer := 1;
    jsrc json_src;
  begin
    --update_decimalpoint();
    --dbms_output.put_line('Using clob');
    jsrc := prepareClob(str);
    tokens := lexer(jsrc);
    if (tokens(indx).type_name = '{') then
      indx := indx + 1;
      obj := parseObj(tokens, indx);
    else
      raise_application_error(-20101, 'JSON Parser exception - no { start found');
    end if;
    if (tokens.count != indx) then
      p_error('} should end the JSON object', tokens(indx));
    end if;

    return obj;
  end parser;

  function parse_any(str varchar2) return pljson_element as
    tokens lTokens;
    obj pljson_list;
    ret pljson_element;
    indx pls_integer := 1;
    jsrc json_src;
  begin
    --update_decimalpoint();
    jsrc := prepareVarchar2(str);
    tokens := lexer(jsrc);
    tokens(tokens.count+1).type_name := ']';
    obj := parseArr(tokens, indx);
    if (tokens.count != indx) then
      p_error('] should end the JSON List object', tokens(indx));
    end if;

    return obj.head();
  end parse_any;

  function parse_any(str clob) return pljson_element as
    tokens lTokens;
    obj pljson_list;
    indx pls_integer := 1;
    jsrc json_src;
  begin
    --update_decimalpoint();
    jsrc := prepareClob(str);
    tokens := lexer(jsrc);
    tokens(tokens.count+1).type_name := ']';
    obj := parseArr(tokens, indx);
    if (tokens.count != indx) then
      p_error('] should end the JSON List object', tokens(indx));
    end if;

    return obj.head();
  end parse_any;

  /* last entry is the one to keep */
  procedure remove_duplicates(obj in out nocopy pljson) as
    type memberlist is table of pljson_element index by varchar2(4000);
    members memberlist;
    nulljsonvalue pljson_element := null;
    validated pljson := pljson();
    indx varchar2(4000);
  begin
    for i in 1 .. obj.count loop
      if (obj.get(i).mapname is null) then
        nulljsonvalue := obj.get(i);
      else
        members(obj.get(i).mapname) := obj.get(i);
      end if;
    end loop;

    validated.check_duplicate(false);
    indx := members.first;
    loop
      exit when indx is null;
      validated.put(indx, members(indx));
      indx := members.next(indx);
    end loop;
    if (nulljsonvalue is not null) then
      validated.put('', nulljsonvalue);
    end if;

    validated.check_for_duplicate := obj.check_for_duplicate;

    obj := validated;
  end;

  function get_version return varchar2 as
  begin
    return 'PL/JSON {{PLJSON_VERSION}}';
  end get_version;

begin
  update_decimalpoint();
end pljson_parser;
/
show err


-- --- pljson_printer.package (spec + body) ---
create or replace package pljson_printer as
  /*
  Copyright (c) 2010 Jonas Krogsboell
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:
  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */
  indent_string varchar2(10 char) := '  '; --chr(9); for tab
  newline_char varchar2(2 char)   := chr(13)||chr(10); -- Windows style
  --newline_char varchar2(2) := chr(10); -- Mac style
  --newline_char varchar2(2) := chr(13); -- Linux style
  ascii_output boolean    not null := true;
  empty_string_as_null boolean not null := false;
  escape_solidus boolean  not null := false;
  unescaped_string_delim varchar2(10 char) := '/**/';

  function pretty_print(obj pljson, spaces boolean default true, line_length number default 0) return varchar2;
  function pretty_print_list(obj pljson_list, spaces boolean default true, line_length number default 0) return varchar2;
  function pretty_print_any(json_part pljson_element, spaces boolean default true, line_length number default 0) return varchar2;
  procedure pretty_print(obj pljson, spaces boolean default true, buf in out nocopy clob, line_length number default 0, erase_clob boolean default true);
  procedure pretty_print_list(obj pljson_list, spaces boolean default true, buf in out nocopy clob, line_length number default 0, erase_clob boolean default true);
  procedure pretty_print_any(json_part pljson_element, spaces boolean default true, buf in out nocopy clob, line_length number default 0, erase_clob boolean default true);

  procedure dbms_output_clob(my_clob clob, delim varchar2, jsonp varchar2 default null);
  procedure htp_output_clob(my_clob clob, jsonp varchar2 default null);
  -- made public just for testing/profiling...
  function escapeString(str varchar2) return varchar2;

end pljson_printer;
/
show err

create or replace package body pljson_printer as
  max_line_len number := 0;
  cur_line_len number := 0;

  -- associative array used inside escapeString to cache the escaped version of every character
  -- escaped so far  (example: char_map('"') contains the  '\"' string)
  -- (if the character does not need to be escaped, the character is stored unchanged in the array itself)
  -- type Rmap_char is record(buf varchar2(40), len integer);
  type Tmap_char_string is table of varchar2(40) index by varchar2(1 char); /* index by unicode char */
  char_map Tmap_char_string;
  -- since char_map the associative array is a global variable reused across multiple calls to escapeString,
  -- i need to be able to detect that the escape_solidus or ascii_output global parameters have been changed,
  -- in order to clear it and avoid using escape sequences that have been cached using the previous values
  char_map_escape_solidus boolean := escape_solidus;
  char_map_ascii_output boolean := ascii_output;

  function llcheck(str in varchar2) return varchar2 as
  begin
    --dbms_output.put_line(cur_line_len || ' : ' || str);
    if (max_line_len > 0 and length(str)+cur_line_len > max_line_len) then
      cur_line_len := length(str);
      return newline_char || str;
    else
      cur_line_len := cur_line_len + length(str);
      return str;
    end if;
  end llcheck;

  -- escapes a single character.
  function escapeChar(ch char) return varchar2 deterministic is
     result varchar2(20);
  begin
      --backspace b = U+0008
      --formfeed  f = U+000C
      --newline   n = U+000A
      --carret    r = U+000D
      --tabulator t = U+0009
      result := ch;

      case ch
      when chr( 8) then result := '\b';
      when chr( 9) then result := '\t';
      when chr(10) then result := '\n';
      when chr(12) then result := '\f';
      when chr(13) then result := '\r';
      when chr(34) then result := '\"';
      when chr(47) then if (escape_solidus) then result := '\/'; end if;
      when chr(92) then result := '\\';
      /* WARNING: ascii() returns PLS_INTEGER and large unicode code points can be negative */
      else if (ascii(ch) >= 0 and ascii(ch) < 32) then
             result :=  '\u' || replace(substr(to_char(ascii(ch), 'XXXX'), 2, 4), ' ', '0');
        elsif (ascii_output) then
             result := replace(asciistr(ch), '\', '\u');
        end if;
      end case;
      return result;
  end;

  function escapeString(str varchar2) return varchar2 as
    sb varchar2(32767 byte) := '';
    buf varchar2(40);
    ch varchar2(1 char); /* unicode char */
  begin
    if (str is null) then return ''; end if;

    -- clear the cache if global parameters have been changed
    if char_map_escape_solidus <> escape_solidus or
       char_map_ascii_output   <> ascii_output
    then
       char_map.delete;
       char_map_escape_solidus := escape_solidus;
       char_map_ascii_output := ascii_output;
    end if;

    for i in 1 .. length(str) loop
      ch := substr(str, i, 1 ) ;

      begin
         -- it this char has already been processed, I have cached its escaped value
         buf:=char_map(ch);
      exception when no_Data_found then
         -- otherwise, i convert the value and add it to the cache
         buf := escapeChar(ch);
         char_map(ch) := buf;
      end;

      sb := sb || buf;
    end loop;
    return sb;
  end escapeString;

  function newline(spaces boolean) return varchar2 as
  begin
    cur_line_len := 0;
    if (spaces) then return newline_char; else return ''; end if;
  end;

/*  function get_schema return varchar2 as
  begin
    return sys_context('userenv', 'current_schema');
  end;
*/
  function tab(indent number, spaces boolean) return varchar2 as
    i varchar(200) := '';
  begin
    if (not spaces) then return ''; end if;
    for x in 1 .. indent loop i := i || indent_string; end loop;
    return i;
  end;

  function string_delim(elem pljson_string) return varchar2 as
  begin
    return
      case when elem.num = 1 then '"'
      else
        case when elem.unescaped_string_delim_p = 1 then elem.unescaped_string_delim
        else pljson_printer.unescaped_string_delim
        end
      end;
  end;
  
  function getCommaSep(spaces boolean) return varchar2 as
  begin
    if (spaces) then return ', '; else return ','; end if;
  end;

  function getMemName(mem pljson_element, spaces boolean) return varchar2 as
  begin
    if (spaces) then
      return llcheck('"'||escapeString(mem.mapname)||'"') || llcheck(' : ');
    else
      return llcheck('"'||escapeString(mem.mapname)||'"') || llcheck(':');
    end if;
  end;


  /* clob methods begin */

  procedure add_to_clob(buf_lob in out nocopy clob, buf_str in out nocopy varchar2, str varchar2) as
  begin
    -- if (length(str) > 5000 - length(buf_str)) then
    if (lengthb(str) > 32767 - lengthb(buf_str)) then
      -- dbms_lob.writeappend(buf_lob, length2(buf_str), buf_str);
      dbms_lob.append(buf_lob, buf_str);
      buf_str := str;
    else
      buf_str := buf_str || str;
    end if;
  end add_to_clob;

  procedure flush_clob(buf_lob in out nocopy clob, buf_str in out nocopy varchar2) as
  begin
    -- dbms_lob.writeappend(buf_lob, length2(buf_str), buf_str);
    dbms_lob.append(buf_lob, buf_str);
  end flush_clob;

  procedure ppObj(obj pljson, indent number, buf in out nocopy clob, spaces boolean, buf_str in out nocopy varchar2);

  procedure ppString(elem pljson_string, buf in out nocopy clob, buf_str in out nocopy varchar2) is
    offset number := 1;
    /* E.I.Sarmas (github.com/dsnz)   2016-01-21   limit to 5000 chars */
    v_str varchar(5000 char);
    amount number := 5000; /* chunk size for use in escapeString, less than this number may be copied */
  begin
    if empty_string_as_null and elem.extended_str is null and elem.str is null then
      add_to_clob(buf, buf_str, 'null');
    else
      add_to_clob(buf, buf_str, string_delim(elem));
      if (elem.extended_str is not null) then --clob implementation
        while (offset <= dbms_lob.getlength(elem.extended_str)) loop
          dbms_lob.read(elem.extended_str, amount, offset, v_str);
          if (elem.num = 1) then
            add_to_clob(buf, buf_str, escapeString(v_str));
          else
            add_to_clob(buf, buf_str, v_str);
          end if;
          offset := offset + amount;
        end loop;
      else
        if (elem.num = 1) then
          while (offset <= length(elem.str)) loop
            v_str:=substr(elem.str, offset, amount);
            add_to_clob(buf, buf_str, escapeString(v_str));
            offset := offset + amount;
          end loop;
        else
          add_to_clob(buf, buf_str, elem.str);
        end if;
      end if;
      add_to_clob(buf, buf_str, string_delim(elem));
    end if;
  end;

  procedure ppEA(input pljson_list, indent number, buf in out nocopy clob, spaces boolean, buf_str in out nocopy varchar2) as
    elem pljson_element;
    arr pljson_element_array := input.list_data;
    numbuf varchar2(4000);
  begin
    for y in 1 .. arr.count loop
      elem := arr(y);
      if (elem is not null) then
      case elem.typeval
        /* number */
        when 4 then
          numbuf := treat(elem as pljson_number).number_toString();
          add_to_clob(buf, buf_str, llcheck(numbuf));
        /* string */
        when 3 then
          ppString(treat(elem as pljson_string), buf, buf_str);
        /* bool */
        when 5 then
          if (elem.get_bool()) then
            add_to_clob(buf, buf_str, llcheck('true'));
          else
            add_to_clob(buf, buf_str, llcheck('false'));
          end if;
        /* null */
        when 6 then
          add_to_clob(buf, buf_str, llcheck('null'));
        /* array */
        when 2 then
          add_to_clob(buf, buf_str, llcheck('['));
          ppEA(treat(elem as pljson_list), indent, buf, spaces, buf_str);
          add_to_clob(buf, buf_str, llcheck(']'));
        /* object */
        when 1 then
          ppObj(treat(elem as pljson), indent, buf, spaces, buf_str);
        else
          add_to_clob(buf, buf_str, llcheck(elem.get_type));
      end case;
      end if;
      if (y != arr.count) then add_to_clob(buf, buf_str, llcheck(getCommaSep(spaces))); end if;
    end loop;
  end ppEA;

  procedure ppMem(mem pljson_element, indent number, buf in out nocopy clob, spaces boolean, buf_str in out nocopy varchar2) as
    numbuf varchar2(4000);
  begin
    add_to_clob(buf, buf_str, llcheck(tab(indent, spaces)) || llcheck(getMemName(mem, spaces)));
    case mem.typeval
      /* number */
      when 4 then
        numbuf := treat(mem as pljson_number).number_toString();
        add_to_clob(buf, buf_str, llcheck(numbuf));
      /* string */
      when 3 then
        ppString(treat(mem as pljson_string), buf, buf_str);
      /* bool */
      when 5 then
        if (mem.get_bool()) then
          add_to_clob(buf, buf_str, llcheck('true'));
        else
          add_to_clob(buf, buf_str, llcheck('false'));
        end if;
      /* null */
      when 6 then
        add_to_clob(buf, buf_str, llcheck('null'));
      /* array */
      when 2 then
        add_to_clob(buf, buf_str, llcheck('['));
        ppEA(treat(mem as pljson_list), indent, buf, spaces, buf_str);
        add_to_clob(buf, buf_str, llcheck(']'));
      /* object */
      when 1 then
        ppObj(treat(mem as pljson), indent, buf, spaces, buf_str);
      else
        add_to_clob(buf, buf_str, llcheck(mem.get_type));
    end case;
  end ppMem;

  procedure ppObj(obj pljson, indent number, buf in out nocopy clob, spaces boolean, buf_str in out nocopy varchar2) as
  begin
    add_to_clob(buf, buf_str, llcheck('{') || newline(spaces));
    for m in 1 .. obj.json_data.count loop
      ppMem(obj.json_data(m), indent+1, buf, spaces, buf_str);
      if (m != obj.json_data.count) then
        add_to_clob(buf, buf_str, llcheck(',') || newline(spaces));
      else
        add_to_clob(buf, buf_str, newline(spaces));
      end if;
    end loop;
    add_to_clob(buf, buf_str, llcheck(tab(indent, spaces)) || llcheck('}')); -- || chr(13);
  end ppObj;

  procedure pretty_print(obj pljson, spaces boolean default true, buf in out nocopy clob, line_length number default 0, erase_clob boolean default true) as
    buf_str varchar2(32767);
    amount number := dbms_lob.getlength(buf);
  begin
    if (erase_clob and amount > 0) then
      dbms_lob.trim(buf, 0);
      -- dbms_lob.erase(buf, amount);
    end if;

    max_line_len := line_length;
    cur_line_len := 0;
    ppObj(obj, 0, buf, spaces, buf_str);
    flush_clob(buf, buf_str);
  end;

  procedure pretty_print_list(obj pljson_list, spaces boolean default true, buf in out nocopy clob, line_length number default 0, erase_clob boolean default true) as
    buf_str varchar2(32767);
    amount number := dbms_lob.getlength(buf);
  begin
    if (erase_clob and amount > 0) then
      dbms_lob.trim(buf, 0);
      -- dbms_lob.erase(buf, amount);
    end if;

    max_line_len := line_length;
    cur_line_len := 0;
    add_to_clob(buf, buf_str, llcheck('['));
    ppEA(obj, 0, buf, spaces, buf_str);
    add_to_clob(buf, buf_str, llcheck(']'));
    flush_clob(buf, buf_str);
  end;

  procedure pretty_print_any(json_part pljson_element, spaces boolean default true, buf in out nocopy clob, line_length number default 0, erase_clob boolean default true) as
    buf_str varchar2(32767) := '';
    numbuf varchar2(4000);
    amount number := dbms_lob.getlength(buf);
  begin
    if (erase_clob and amount > 0) then
      dbms_lob.trim(buf, 0);
      -- dbms_lob.erase(buf, amount);
    end if;

    case json_part.typeval
      /* number */
      when 4 then
        numbuf := treat(json_part as pljson_number).number_toString();
        add_to_clob(buf, buf_str, numbuf);
      /* string */
      when 3 then
        ppString(treat(json_part as pljson_string), buf, buf_str);
      /* bool */
      when 5 then
        if (json_part.get_bool()) then
          add_to_clob(buf, buf_str, 'true');
        else
          add_to_clob(buf, buf_str, 'false');
        end if;
      /* null */
      when 6 then
        add_to_clob(buf, buf_str, 'null');
      /* array */
      when 2 then
        pretty_print_list(pljson_list(json_part), spaces, buf, line_length, erase_clob);
        return;
      /* object */
      when 1 then
        pretty_print(pljson(json_part), spaces, buf, line_length, erase_clob);
        return;
      else
        add_to_clob(buf, buf_str, 'unknown type:' || json_part.get_type);
    end case;
    flush_clob(buf, buf_str);
  end;

  /* clob methods end */


  /* varchar2 methods begin */

  procedure add_buf (buf in out nocopy varchar2, str in varchar2) as
  begin
    if (lengthb(str)>32767-lengthb(buf)) then
      raise_application_error(-20001,'Length of result JSON more than 32767 bytes. Use to_clob() procedures');
    end if;
    buf := buf || str;
  end;

  procedure ppString(elem pljson_string, buf in out nocopy varchar2) is
    offset number := 1;
    /* E.I.Sarmas (github.com/dsnz)   2016-01-21   limit to 5000 chars */
    v_str varchar(5000 char);
    amount number := 5000; /* chunk size for use in escapeString, less than this number may be copied */
  begin
    if empty_string_as_null and elem.extended_str is null and elem.str is null then
      add_buf(buf, 'null');
    else
      add_buf(buf, string_delim(elem));
      if (elem.extended_str is not null) then --clob implementation
        while (offset <= dbms_lob.getlength(elem.extended_str)) loop
          dbms_lob.read(elem.extended_str, amount, offset, v_str);
          if (elem.num = 1) then
            add_buf(buf, escapeString(v_str));
          else
            add_buf(buf, v_str);
          end if;
          offset := offset + amount;
        end loop;
      else
        if (elem.num = 1) then
          while (offset <= length(elem.str)) loop
            v_str:=substr(elem.str, offset, amount);
            add_buf(buf, escapeString(v_str));
            offset := offset + amount;
          end loop;
        else
          add_buf(buf, elem.str);
        end if;
      end if;
      add_buf(buf, string_delim(elem));
    end if;
  end;

  procedure ppObj(obj pljson, indent number, buf in out nocopy varchar2, spaces boolean);

  procedure ppEA(input pljson_list, indent number, buf in out varchar2, spaces boolean) as
    elem pljson_element;
    arr pljson_element_array := input.list_data;
    str varchar2(400);
  begin
    for y in 1 .. arr.count loop
      elem := arr(y);
      if (elem is not null) then
      case elem.typeval
        /* number */
        when 4 then
          str := treat(elem as pljson_number).number_toString();
          add_buf(buf, llcheck(str));
        /* string */
        when 3 then
          ppString(treat(elem as pljson_string), buf);
        /* bool */
        when 5 then
          if (elem.get_bool()) then
            add_buf (buf, llcheck('true'));
          else
            add_buf (buf, llcheck('false'));
          end if;
        /* null */
        when 6 then
          add_buf (buf, llcheck('null'));
        /* array */
        when 2 then
          add_buf( buf, llcheck('['));
          ppEA(treat(elem as pljson_list), indent, buf, spaces);
          add_buf( buf, llcheck(']'));
        /* object */
        when 1 then
          ppObj(treat(elem as pljson), indent, buf, spaces);
        else
          add_buf (buf, llcheck(elem.get_type)); /* should never happen */
      end case;
      end if;
      if (y != arr.count) then add_buf(buf, llcheck(getCommaSep(spaces))); end if;
    end loop;
  end ppEA;

  procedure ppMem(mem pljson_element, indent number, buf in out nocopy varchar2, spaces boolean) as
    str varchar2(400) := '';
  begin
    add_buf(buf, llcheck(tab(indent, spaces)) || getMemName(mem, spaces));
    case mem.typeval
      /* number */
      when 4 then
        str := treat(mem as pljson_number).number_toString();
        add_buf(buf, llcheck(str));
      /* string */
      when 3 then
        ppString(treat(mem as pljson_string), buf);
      /* bool */
      when 5 then
        if (mem.get_bool()) then
          add_buf(buf, llcheck('true'));
        else
          add_buf(buf, llcheck('false'));
        end if;
      /* null */
      when 6 then
        add_buf(buf, llcheck('null'));
      /* array */
      when 2 then
        add_buf(buf, llcheck('['));
        ppEA(treat(mem as pljson_list), indent, buf, spaces);
        add_buf(buf, llcheck(']'));
      /* object */
      when 1 then
        ppObj(treat(mem as pljson), indent, buf, spaces);
      else
        add_buf(buf, llcheck(mem.get_type)); /* should never happen */
    end case;
  end ppMem;

  procedure ppObj(obj pljson, indent number, buf in out nocopy varchar2, spaces boolean) as
  begin
    add_buf (buf, llcheck('{') || newline(spaces));
    for m in 1 .. obj.json_data.count loop
      ppMem(obj.json_data(m), indent+1, buf, spaces);
      if (m != obj.json_data.count) then
        add_buf(buf, llcheck(',') || newline(spaces));
      else
        add_buf(buf, newline(spaces));
      end if;
    end loop;
    add_buf(buf, llcheck(tab(indent, spaces)) || llcheck('}')); -- || chr(13);
  end ppObj;

  function pretty_print(obj pljson, spaces boolean default true, line_length number default 0) return varchar2 as
    buf varchar2(32767 byte) := '';
  begin
    max_line_len := line_length;
    cur_line_len := 0;
    ppObj(obj, 0, buf, spaces);
    return buf;
  end pretty_print;

  function pretty_print_list(obj pljson_list, spaces boolean default true, line_length number default 0) return varchar2 as
    buf varchar2(32767 byte) :='';
  begin
    max_line_len := line_length;
    cur_line_len := 0;
    add_buf(buf, llcheck('['));
    ppEA(obj, 0, buf, spaces);
    add_buf(buf, llcheck(']'));
    return buf;
  end;

  function pretty_print_any(json_part pljson_element, spaces boolean default true, line_length number default 0) return varchar2 as
    buf varchar2(32767) := '';
  begin
    case json_part.typeval
      /* number */
      when 4 then
        buf := treat(json_part as pljson_number).number_toString();
      /* string */
      when 3 then
        ppString(treat(json_part as pljson_string), buf);
      /* bool */
      when 5 then
        if (json_part.get_bool()) then buf := 'true'; else buf := 'false'; end if;
      /* null */
      when 6 then
        buf := 'null';
      /* array */
      when 2 then
        buf := pretty_print_list(pljson_list(json_part), spaces, line_length);
      /* object */
      when 1 then
        buf := pretty_print(pljson(json_part), spaces, line_length);
      else
        buf := 'weird error: ' || json_part.get_type;
    end case;
    return buf;
  end;

  /* varchar2 methods end */


  procedure dbms_output_clob(my_clob clob, delim varchar2, jsonp varchar2 default null) as
    prev number := 1;
    indx number := 1;
    size_of_nl number := length2(delim);
    v_str varchar2(32767);
    amount number;
    max_string_chars number := 5000; /* chunk size, less than this number may be copied */
  begin
    if (jsonp is not null) then dbms_output.put_line(jsonp||'('); end if;
    while (indx != 0) loop
      --read every line
      indx := dbms_lob.instr(my_clob, delim, prev+1);
      --dbms_output.put_line(prev || ' to ' || indx);

      if (indx = 0) then
        --emit from prev to end;
        amount := max_string_chars;
        --dbms_output.put_line(' mycloblen ' || dbms_lob.getlength(my_clob));
        loop
          dbms_lob.read(my_clob, amount, prev, v_str);
          dbms_output.put_line(v_str);
          prev := prev+amount;
          exit when prev >= dbms_lob.getlength(my_clob);
        end loop;
      else
        amount := indx - prev;
        if (amount > max_string_chars) then
          amount := max_string_chars;
          --dbms_output.put_line(' mycloblen ' || dbms_lob.getlength(my_clob));
          loop
            dbms_lob.read(my_clob, amount, prev, v_str);
            dbms_output.put_line(v_str);
            prev := prev+amount;
            amount := indx - prev;
            exit when prev >= indx - 1;
            if (amount > max_string_chars) then
              amount := max_string_chars;
            end if;
          end loop;
          prev := indx + size_of_nl;
        else
          dbms_lob.read(my_clob, amount, prev, v_str);
          dbms_output.put_line(v_str);
          prev := indx + size_of_nl;
        end if;
      end if;

    end loop;
    if (jsonp is not null) then dbms_output.put_line(')'); end if;

/*    while (amount != 0) loop
      indx := dbms_lob.instr(my_clob, delim, prev+1);

--      dbms_output.put_line(prev || ' to ' || indx);
      if (indx = 0) then
        indx := dbms_lob.getlength(my_clob)+1;
      end if;
      if (indx-prev > 32767) then
        indx := prev+32767;
      end if;
--      dbms_output.put_line(prev || ' to ' || indx);
      --substr doesnt work properly on all platforms! (come on oracle - error on Oracle VM for virtualbox)
--        dbms_output.put_line(dbms_lob.substr(my_clob, indx-prev, prev));
      amount := indx-prev;
--        dbms_output.put_line('amount'||amount);
      dbms_lob.read(my_clob, amount, prev, v_str);
      dbms_output.put_line(v_str);
      prev := indx+size_of_nl;
      if (amount = 32767) then prev := prev-size_of_nl-1; end if;
    end loop;
    if (jsonp is not null) then dbms_output.put_line(')'); end if;*/
  end;

/*
procedure dbms_output_clob(my_clob clob, delim varchar2, jsonp varchar2 default null) as
    prev number := 1;
    indx number := 1;
    size_of_nl number := length2(delim);
    v_str varchar2(32767);
    amount number;
  begin
    if (jsonp is not null) then dbms_output.put_line(jsonp||'('); end if;
    while (indx != 0) loop
      indx := dbms_lob.instr(my_clob, delim, prev+1);

      --dbms_output.put_line(prev || ' to ' || indx);
      if (indx-prev > 32767) then
        indx := prev+32767;
      end if;
      --dbms_output.put_line(prev || ' to ' || indx);
      --substr doesnt work properly on all platforms! (come on oracle - error on Oracle VM for virtualbox)
      if (indx = 0) then
        --dbms_output.put_line(dbms_lob.substr(my_clob, dbms_lob.getlength(my_clob)-prev+size_of_nl, prev));
        amount := dbms_lob.getlength(my_clob)-prev+size_of_nl;
        dbms_lob.read(my_clob, amount, prev, v_str);
      else
        --dbms_output.put_line(dbms_lob.substr(my_clob, indx-prev, prev));
        amount := indx-prev;
        --dbms_output.put_line('amount'||amount);
        dbms_lob.read(my_clob, amount, prev, v_str);
      end if;
      dbms_output.put_line(v_str);
      prev := indx+size_of_nl;
      if (amount = 32767) then prev := prev-size_of_nl-1; end if;
    end loop;
    if (jsonp is not null) then dbms_output.put_line(')'); end if;
  end;
*/

  procedure htp_output_clob(my_clob clob, jsonp varchar2 default null) as
    /*amount number := 4096;
    pos number := 1;
    len number;
    */
    l_amt    number default 4096;
    l_off   number default 1;
    l_str   varchar2(32000);
  begin
    if (jsonp is not null) then htp.prn(jsonp||'('); end if;

    begin
      loop
        dbms_lob.read( my_clob, l_amt, l_off, l_str );

        -- it is vital to use htp.PRN to avoid
        -- spurious line feeds getting added to your
        -- document
        htp.prn( l_str  );
        l_off := l_off+l_amt;
      end loop;
    exception
      when no_data_found then NULL;
    end;

    /*
    len := dbms_lob.getlength(my_clob);

    while (pos < len) loop
      htp.prn(dbms_lob.substr(my_clob, amount, pos)); -- should I replace substr with dbms_lob.read?
      --dbms_output.put_line(dbms_lob.substr(my_clob, amount, pos));
      pos := pos + amount;
    end loop;
    */
    if (jsonp is not null) then htp.prn(')'); end if;
  end;

end pljson_printer;
/
show err

-- --- pljson_ext.impl (body) ---
create or replace package body pljson_ext as
  /*
  Copyright (c) 2009 Jonas Krogsboell

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  */

  scanner_exception exception;
  pragma exception_init(scanner_exception, -20100);
  parser_exception exception;
  pragma exception_init(parser_exception, -20101);
  jext_exception exception;
  pragma exception_init(jext_exception, -20110);

  --extra function checks if number has no fraction
  function is_integer(v pljson_element) return boolean as
    num number;
    num_double binary_double;
    int_number number(38); --the oracle way to specify an integer
    int_double binary_double; --the oracle way to specify an integer
  begin
    /*
    if (v.is_number()) then
      myint := v.get_number();
      return (myint = v.get_number()); --no rounding errors?
    else
      return false;
    end if;
    */
    if (not v.is_number()) then
      raise_application_error(-20109, 'not a number-value');
    end if;
    /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
    if (v.is_number_repr_number()) then
      num := v.get_number();
      int_number := trunc(num);
      --dbms_output.put_line('number: ' || num || ' -> ' || int_number);
      return (int_number = num); --no rounding errors?
    elsif (v.is_number_repr_double()) then
      num_double := v.get_double();
      int_double := trunc(num_double);
      --dbms_output.put_line('double: ' || num_double || ' -> ' || int_double);
      return (int_double = num_double); --no rounding errors?
    else
      return false;
    end if;
  end;

  --extension enables json to store dates without compromising the implementation
  function to_json_string(d date) return pljson_string as
  begin
    return pljson_string(to_char(d, format_string));
  end;

  --notice that a date type in json is also a varchar2
  function is_date(v pljson_element) return boolean as
    temp date;
  begin
    temp := pljson_ext.to_date(v);
    return true;
  exception
    when others then
      return false;
  end;

  --conversion is needed to extract dates
  function to_date(v pljson_element) return date as
  begin
    if (v.is_string()) then
      return standard.to_date(v.get_string(), format_string);
    else
      raise_application_error(-20110, 'not a date-value');
    end if;
  exception
    when others then
      raise_application_error(-20110, 'Anydata did not contain a date on the format: '||format_string);
  end;

  -- alias so that old code doesn't break
  function to_date2(v pljson_element) return date as
  begin
    return to_date(v);
  end;

  /*
    assumes single base64 string or broken into equal length lines of max 64 or 76 chars
    (as specified by RFC-1421 or RFC-2045)
    line ending can be CR+NL or NL
  */
  function decodeBase64Clob2Blob(p_clob clob) return blob
  is
    r_blob blob;
    clob_size number;
    pos number;
    c_buf varchar2(32767);
    r_buf raw(32767);
    v_read_size number;
    v_line_size number;
  begin
    dbms_lob.createtemporary(r_blob, false, dbms_lob.call);
    /*
      E.I.Sarmas (github.com/dsnz)   2017-12-07   fix for alignment issues
      assumes single base64 string or broken into equal length lines of max 64 or 76 followed by CR+NL
      as specified by RFC-1421 or RFC-2045 which seem to be the supported ones by Oracle utl_encode
      also support single NL instead of CR+NL !
    */
    clob_size := dbms_lob.getlength(p_clob);
    v_line_size := 64;
    if clob_size >= 65 and dbms_lob.substr(p_clob, 1, 65) = chr(10) then
      v_line_size := 65;
    elsif clob_size >= 66 and dbms_lob.substr(p_clob, 1, 65) = chr(13) then
      v_line_size := 66;
    elsif clob_size >= 77 and dbms_lob.substr(p_clob, 1, 77) = chr(10) then
      v_line_size := 77;
    elsif clob_size >= 78 and dbms_lob.substr(p_clob, 1, 77) = chr(13) then
      v_line_size := 78;
    end if;
    --dbms_output.put_line('decoding in multiples of ' || v_line_size);
    v_read_size := floor(32767/v_line_size)*v_line_size;

    pos := 1;
    while (pos < clob_size) loop
      dbms_lob.read(p_clob, v_read_size, pos, c_buf);
      r_buf := utl_encode.base64_decode(utl_raw.cast_to_raw(c_buf));
      dbms_lob.writeappend(r_blob, utl_raw.length(r_buf), r_buf);
      pos := pos + v_read_size;
    end loop;
    return r_blob;
  end decodeBase64Clob2Blob;

  /*
    encoding in lines of 64 chars ending with CR+NL
  */
  function encodeBase64Blob2Clob(p_blob in  blob) return clob
  is
    r_clob clob;
    /* E.I.Sarmas (github.com/dsnz)   2017-12-07   NOTE: must be multiple of 48 !!! */
    c_step number := 12000;
    c_buf varchar2(32767);
  begin
    if p_blob is not null then
      dbms_lob.createtemporary(r_clob, false, dbms_lob.call);
      for i in 0 .. trunc((dbms_lob.getlength(p_blob) - 1)/c_step) loop
        c_buf := utl_raw.cast_to_varchar2(utl_encode.base64_encode(dbms_lob.substr(p_blob, c_step, i * c_step + 1)));
        /*
          E.I.Sarmas (github.com/dsnz)   2017-12-07   fix for alignment issues
          must output CR+NL at end always, so will align with the following block and can be decoded correctly
          assumes ending in CR+NL
        */
        if substr(c_buf, length(c_buf)) != chr(10) then
          c_buf := c_buf || CHR(13) || CHR(10);
        end if;
        /*
        dbms_output.put_line(
          'l=' || length(c_buf) ||
          ' e=' || ascii(substr(c_buf, length(c_buf) - 1)) || ' ' || ascii(substr(c_buf, length(c_buf)))
        );
        */
        dbms_lob.writeappend(lob_loc => r_clob, amount => length(c_buf), buffer => c_buf);
      end loop;
    end if;
    return r_clob;
  end encodeBase64Blob2Clob;

  --Json Path parser
  /* E.I.Sarmas (github.com/dsnz)   2021-12-01   minor path enhancement and more correct enforcement of paths accepted */
  /*

    updated definition of json path expression syntax accepted by PLJSON

    - a path may optionally begin with $ indicating the JSON object to be matched (root)
      then it's followed by 0 or more path steps
      each step can be an object step or an array step, depending on whether the context item represents a JSON object or a JSON array

    - an object step is a period (.), sometimes read as "dot", followed by an object field name (object property name)
    a field name must start with an uppercase or lowercase letter A to Z and contain only such letters or decimal digits (0-9),
    or else it must be enclosed in double quotation marks (")
    OR
    a left bracket ([) followed by a a field name enclosed in single (') or double (") quotes, followed by a right bracket (])

    - an array step is a left bracket ([) followed by a single numeric index, followed by a right bracket (])
    array indexing is one-based (1, 2, 3,...)

    examples:
      $.store.book[0].title
      $['store']['book'][0]['title']

    in latest update
    - an object step, beginning with dot (.), now accepts name within double quotes (")
    - no longer accepts name beginning with, ending with and including spaces eg. 'd.  a name  .data'

    - in past, after a dot (.) the field name could start with space or number
      and include or end with any number of spaces
      now this is not allowed, unquoted field names must begin with an alpha character or _
      and contain only alphanumeric characters

    - path expressions are now compatible with Oracle Basic SQL/JSON Path Expression Syntax
      but excluding the optional filter expression and the optional function step at end

  */
  function parsePath(json_path varchar2, base number default 1) return pljson_list as
    build_path varchar2(32767) := '[';
    buf varchar2(4);
    endstring varchar2(1);
    indx number := 1;
    ret pljson_list;

    procedure next_char as
    begin
      if (indx <= length(json_path)) then
        buf := substr(json_path, indx, 1);
        indx := indx + 1;
      else
        buf := null;
      end if;
    end;
    --skip ws
    procedure skipws as begin while (buf in (chr(9), chr(10), chr(13), ' ')) loop next_char; end loop; end;

  begin
    -- dbms_output.put_line('parse: ' || json_path);

    -- handle null path and optional '$' at beginning
    if json_path is null or substr(json_path, 1, 1) = '$' then
      indx := 2;
      next_char();
    else
      if substr(json_path, 1, 1) = '[' then
        next_char();
      else
        buf := '.';
      end if;
    end if;

    while (buf is not null) loop
      -- dbms_output.put_line(build_path || ' + ' || buf);

      if (buf = '.') then
        next_char();
        if (buf is null) then raise_application_error(-20110, 'JSON Path parse error: . is not a valid json_path end'); end if;
        /* E.I.Sarmas (github.com/dsnz)   2021-10-31   removed space or number as acceptable character */
        if (not regexp_like(buf, '^["[:alpha:]\_]+', 'c') ) then
          -- dbms_output.put_line(build_path || ' + ' || buf);
          raise_application_error(-20110, 'JSON Path parse error: alpha or _ character expected at position '||indx);
        end if;

        if (build_path != '[') then build_path := build_path || ','; end if;
        build_path := build_path || '"';
        /* E.I.Sarmas (github.com/dsnz)   2021-10-31   accept name with any characters quoted within "" after . */
        if buf = '"' then
          next_char();
          while buf is not null and buf != '"' loop
            build_path := build_path || buf;
            next_char();
          end loop;
          if buf is null then
            raise_application_error(-20110, 'JSON Path parse error: premature json_path end, missing ending "');
          end if;
          next_char();
        else
          /* E.I.Sarmas (github.com/dsnz)   2021-10-31   removed space as acceptable character */
          while (regexp_like(buf, '^[[:alnum:]\_]+', 'c') ) loop
            build_path := build_path || buf;
            next_char();
          end loop;
        end if;
        build_path := build_path || '"';

      elsif (buf = '[') then
        next_char();
        skipws();
        if (buf is null) then raise_application_error(-20110, 'JSON Path parse error: [ is not a valid json_path end'); end if;
        -- array step
        if (buf in ('1','2','3','4','5','6','7','8','9') or (buf = '0' and base = 0)) then
          if (build_path != '[') then build_path := build_path || ','; end if;
          while (buf in ('0','1','2','3','4','5','6','7','8','9')) loop
            build_path := build_path || buf;
            next_char();
          end loop;
        -- object step using [] syntax
        elsif (regexp_like(buf, '^(\"|\'')', 'c')) then
          endstring := buf;
          if (build_path != '[') then build_path := build_path || ','; end if;
          build_path := build_path || '"';
          next_char();
          if (buf is null) then raise_application_error(-20110, 'JSON Path parse error: premature json_path end'); end if;
          while (buf != endstring) loop
            build_path := build_path || buf;
            next_char();
            if (buf is null) then raise_application_error(-20110, 'JSON Path parse error: premature json_path end'); end if;
            if (buf = '\') then
              next_char();
              build_path := build_path || '\' || buf;
              next_char();
            end if;
          end loop;
          build_path := build_path || '"';
          next_char();
        else
          raise_application_error(-20110, 'JSON Path parse error: expected a string or a positive integer at '||indx);
        end if;
        skipws();
        if (buf is null) then raise_application_error(-20110, 'JSON Path parse error: premature json_path end'); end if;
        if (buf != ']') then raise_application_error(-20110, 'JSON Path parse error: no array ending found. found: '|| buf); end if;
        next_char();
        skipws();

      /* E.I.Sarmas (github.com/dsnz)   2021-10-31   obsolete, repeats code after ".", handled by assuming a dummy "." at start
      elsif (build_path = '[') then
        if (not regexp_like(buf, '^[[:alnum:]\_ ]+', 'c') ) then
          raise_application_error(-20110, 'JSON Path parse error: alpha-numeric character or space expected at position '||indx);
        end if;
        build_path := build_path || '"';
        while (regexp_like(buf, '^[[:alnum:]\_ ]+', 'c') ) loop
          build_path := build_path || buf;
          next_char();
        end loop;
        build_path := build_path || '"';
      */
      else
        raise_application_error(-20110, 'JSON Path parse error: expected . or [ found '|| buf || ' at position '|| indx);
      end if;

    end loop;

    build_path := build_path || ']';
    build_path := replace(replace(replace(replace(replace(build_path, chr(9), '\t'), chr(10), '\n'), chr(13), '\f'), chr(8), '\b'), chr(14), '\r');
    -- dbms_output.put_line('parse= ' || build_path);

    ret := pljson_list(build_path);
    if (base != 1) then
      --fix base 0 to base 1
      declare
        elem pljson_element;
      begin
        for i in 1 .. ret.count loop
          elem := ret.get(i);
          if (elem.is_number()) then
            ret.replace(i, elem.get_number()+1);
          end if;
        end loop;
      end;
    end if;

    return ret;
  end parsePath;

  --JSON pre-parsed path getters
  /* contributed by @asfernandes */
  function get_json_element(obj pljson, path pljson_list) return pljson_element as
    path_segments pljson_path := pljson_path();
    ret pljson_element;
  begin
    if (path.count = 0) then
      return obj;
    end if;

    for i in 1 .. path.count loop
      path_segments.extend;
      if (path.get(i).is_number()) then
        path_segments(path_segments.count) := pljson_path_segment(path.get(i).get_number(), null);
      else
        path_segments(path_segments.count) := pljson_path_segment(null, path.get(i).get_string());
      end if;
    end loop;

    obj.get_internal_path(path_segments, 1, ret);
    return ret;
  exception
    when scanner_exception then raise;
    when parser_exception then raise;
    when jext_exception then raise;
    when others then return null;
  end get_json_element;

  function get_string(obj pljson, path pljson_list) return varchar2 as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not temp.is_string()) then
      return null;
    else
      return temp.get_string();
    end if;
  end;

  function get_number(obj pljson, path pljson_list) return number as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not temp.is_number()) then
      return null;
    else
      return temp.get_number();
    end if;
  end;

  function get_double(obj pljson, path pljson_list) return binary_double as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not temp.is_number()) then
      return null;
    else
      return temp.get_double();
    end if;
  end;

  function get_json(obj pljson, path pljson_list) return pljson as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not temp.is_object()) then
      return null;
    else
      return treat(temp as pljson);
    end if;
  end;

  function get_json_list(obj pljson, path pljson_list) return pljson_list as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not temp.is_array()) then
      return null;
    else
      return treat(temp as pljson_list);
    end if;
  end;

  function get_bool(obj pljson, path pljson_list) return boolean as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not temp.is_bool()) then
      return null;
    else
      return temp.get_bool();
    end if;
  end;

  function get_date(obj pljson, path pljson_list) return date as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path);
    if (temp is null or not is_date(temp)) then
      return null;
    else
      return pljson_ext.to_date(temp);
    end if;
  end;

  --JSON Path getters
  --saved original code, in case of future bug troubleshooting
  function get_json_element_original(obj pljson, v_path varchar2, base number default 1) return pljson_element as
    path pljson_list;
    ret pljson_element;
    o pljson; l pljson_list;
  begin
    path := parsePath(v_path, base);
    ret := obj;
    if (path.count = 0) then return ret; end if;

    for i in 1 .. path.count loop
      if (path.get(i).is_string()) then
        --string fetch only on json
        ------o := pljson(ret);
        ------ret := o.get(path.get(i).get_string());
        /* E.I.Sarmas (github.com/dsnz)   2020-04-18   use inheritance and avoid treat() */
        ret := ret.get(path.get(i).get_string());
        --experimental, ignore
        --ret := get_piece(o, path.get(i).get_string());
      else
        --number fetch on json and json_list
        if (ret.is_array()) then
          ------l := pljson_list(ret);
          ------ret := l.get(path.get(i).get_number());
          /* E.I.Sarmas (github.com/dsnz)   2020-04-18   use inheritance and avoid treat() */
          ret := ret.get(path.get(i).get_number());
          --experimental, ignore
          --ret := get_piece(l, path.get(i).get_number());
        else
          ------o := pljson(ret);
          ------l := o.get_values();
          ------ret := l.get(path.get(i).get_number());
          /* E.I.Sarmas (github.com/dsnz)   2020-04-18   use inheritance and avoid treat() */
          ret := ret.get(path.get(i).get_number());
          --experimental, ignore
          --ret := get_piece(l, path.get(i).get_number());
        end if;
      end if;
    end loop;

    return ret;
  exception
    when scanner_exception then raise;
    when parser_exception then raise;
    when jext_exception then raise;
    when others then return null;
  end get_json_element_original;

  function get_json_element(obj pljson, v_path varchar2, base number default 1) return pljson_element as
    path pljson_list;
  begin
    path := parsePath(v_path, base);
    return get_json_element(obj, path);
  end get_json_element;

  function get_string(obj pljson, path varchar2, base number default 1) return varchar2 as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not temp.is_string()) then
      return null;
    else
      return temp.get_string();
    end if;
  end;

  function get_number(obj pljson, path varchar2, base number default 1) return number as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not temp.is_number()) then
      return null;
    else
      return temp.get_number();
    end if;
  end;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  function get_double(obj pljson, path varchar2, base number default 1) return binary_double as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not temp.is_number()) then
      return null;
    else
      return temp.get_double();
    end if;
  end;

  function get_json(obj pljson, path varchar2, base number default 1) return pljson as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not temp.is_object()) then
      return null;
    else
      return treat(temp as pljson);
    end if;
  end;

  function get_json_list(obj pljson, path varchar2, base number default 1) return pljson_list as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not temp.is_array()) then
      return null;
    else
      return treat(temp as pljson_list);
    end if;
  end;

  function get_bool(obj pljson, path varchar2, base number default 1) return boolean as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not temp.is_bool()) then
      return null;
    else
      return temp.get_bool();
    end if;
  end;

  function get_date(obj pljson, path varchar2, base number default 1) return date as
    temp pljson_element;
  begin
    temp := get_json_element(obj, path, base);
    if (temp is null or not is_date(temp)) then
      return null;
    else
      return pljson_ext.to_date(temp);
    end if;
  end;

  /* JSON pre-parsed path putter internal function */
  procedure put_internal_preparsed(obj in out nocopy pljson, path pljson_list, elem pljson_element) as
    path_segments pljson_path := pljson_path();
    dummy boolean;
  begin
    if (path.count = 0) then raise_application_error(-20110, 'PLJSON_EXT put error: cannot put with empty string.'); end if;

    for i in 1 .. path.count loop
      path_segments.extend;

      if (path.get(i).is_number()) then
        path_segments(path_segments.count) := pljson_path_segment(path.get(i).get_number(), null);
      else
        path_segments(path_segments.count) := pljson_path_segment(null, path.get(i).get_string());
      end if;
    end loop;

    dummy := obj.put_internal_path(path_segments, elem, 1);
  end;

  /* JSON Path putter internal function */
  --saved original code, in case of future bug troubleshooting
  procedure put_internal_original(obj in out nocopy pljson, v_path varchar2, elem pljson_element, base number) as
    val pljson_element := elem;
    path pljson_list;
    backreference pljson_list := pljson_list();

    keyval pljson_element; keynum number; keystring varchar2(4000);
    temp pljson_element := obj;
    obj_temp  pljson;
    list_temp pljson_list;
    inserter pljson_element;
  begin
    path := pljson_ext.parsePath(v_path, base);
    if (path.count = 0) then raise_application_error(-20110, 'PLJSON_EXT put error: cannot put with empty string.'); end if;

    --build backreference
    for i in 1 .. path.count loop
      --backreference.print(false);
      keyval := path.get(i);
      if (keyval.is_number()) then
        --number index
        keynum := keyval.get_number();
        if ((not temp.is_object()) and (not temp.is_array())) then
          if (val is null) then return; end if;
          backreference.remove_last;
          temp := pljson_list();
          backreference.append(temp);
        end if;

        if (temp.is_object()) then
          obj_temp := pljson(temp);
          if (obj_temp.count < keynum) then
            if (val is null) then return; end if;
            raise_application_error(-20110, 'PLJSON_EXT put error: access object with too few members.');
          end if;
          temp := obj_temp.get(keynum);
        else
          list_temp := pljson_list(temp);
          if (list_temp.count < keynum) then
            if (val is null) then return; end if;
            --raise error or quit if val is null
            for i in list_temp.count+1 .. keynum loop
              list_temp.append(pljson_null());
            end loop;
            backreference.remove_last;
            backreference.append(list_temp);
          end if;

          temp := list_temp.get(keynum);
        end if;
      else
        --string index
        keystring := keyval.get_string();
        if (not temp.is_object()) then
          --backreference.print;
          if (val is null) then return; end if;
          backreference.remove_last;
          temp := pljson();
          backreference.append(temp);
          --raise_application_error(-20110, 'PLJSON_EXT put error: trying to access a non object with a string.');
        end if;
        obj_temp := pljson(temp);
        temp := obj_temp.get(keystring);
      end if;

      if (temp is null) then
        if (val is null) then return; end if;
        --what to expect?
        keyval := path.get(i+1);
        if (keyval is not null and keyval.is_number()) then
          temp := pljson_list();
        else
          temp := pljson();
        end if;
      end if;
      backreference.append(temp);
    end loop;

    --  backreference.print(false);
    --  path.print(false);

    --use backreference and path together
    inserter := val;
    for i in reverse 1 .. backreference.count loop
      -- inserter.print(false);
      if ( i = 1 ) then
        keyval := path.get(1);
        if (keyval.is_string()) then
          keystring := keyval.get_string();
        else
          keynum := keyval.get_number();
          declare
            t1 pljson_element := obj.get(keynum);
          begin
            keystring := t1.mapname;
          end;
        end if;
        if (inserter is null) then obj.remove(keystring); else obj.put(keystring, inserter); end if;
      else
        temp := backreference.get(i-1);
        if (temp.is_object()) then
          keyval := path.get(i);
          obj_temp := pljson(temp);
          if (keyval.is_string()) then
            keystring := keyval.get_string();
          else
            keynum := keyval.get_number();
            declare
              t1 pljson_element := obj_temp.get(keynum);
            begin
              keystring := t1.mapname;
            end;
          end if;
          if (inserter is null) then
            obj_temp.remove(keystring);
            if (obj_temp.count > 0) then inserter := obj_temp; end if;
          else
            obj_temp.put(keystring, inserter);
            inserter := obj_temp;
          end if;
        else
          --array only number
          keynum := path.get(i).get_number();
          list_temp := pljson_list(temp);
          list_temp.remove(keynum);
          if (not inserter is null) then
            list_temp.append(inserter, keynum);
            inserter := list_temp;
          else
            if (list_temp.count > 0) then inserter := list_temp; end if;
          end if;
        end if;
      end if;

    end loop;

  end put_internal_original;

  procedure put_internal(obj in out nocopy pljson, v_path varchar2, elem pljson_element, base number) as
    path pljson_list;
  begin
    path := pljson_ext.parsePath(v_path, base);
    put_internal_preparsed(obj, path, elem);
  end put_internal;

  /* JSON pre-parsed path putters */
  procedure put(obj in out nocopy pljson, path pljson_list, elem varchar2) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, pljson_string(elem));
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem number) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, pljson_number(elem));
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem binary_double) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, pljson_number(elem));
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem pljson) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, elem);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem pljson_list) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, elem);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem boolean) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, pljson_bool(elem));
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem pljson_element) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, elem);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path pljson_list, elem date) as
  begin
    if elem is null then
      put_internal_preparsed(obj, path, pljson_null());
    else
      put_internal_preparsed(obj, path, pljson_ext.to_json_string(elem));
    end if;
  end;

  /* JSON Path putters */
  procedure put(obj in out nocopy pljson, path varchar2, elem varchar2, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, pljson_string(elem), base);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path varchar2, elem number, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, pljson_number(elem), base);
    end if;
  end;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  procedure put(obj in out nocopy pljson, path varchar2, elem binary_double, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, pljson_number(elem), base);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path varchar2, elem pljson, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, elem, base);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path varchar2, elem pljson_list, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, elem, base);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path varchar2, elem boolean, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, pljson_bool(elem), base);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path varchar2, elem pljson_element, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, elem, base);
    end if;
  end;

  procedure put(obj in out nocopy pljson, path varchar2, elem date, base number default 1) as
  begin
    if elem is null then
      put_internal(obj, path, pljson_null(), base);
    else
      put_internal(obj, path, pljson_ext.to_json_string(elem), base);
    end if;
  end;

  procedure remove(obj in out nocopy pljson, path pljson_list) as
  begin
    pljson_ext.put_internal_preparsed(obj, path, null);
  end remove;

  procedure remove(obj in out nocopy pljson, path varchar2, base number default 1) as
  begin
    pljson_ext.put_internal(obj, path, null, base);
    --if (json_ext.get_json_element(obj, path) is not null) then
    --end if;
  end remove;

  --Pretty print with JSON Path
  function pp(obj pljson, v_path varchar2) return varchar2 as
    json_part pljson_element;
  begin
    json_part := pljson_ext.get_json_element(obj, v_path);
    if (json_part is null) then
      return '';
    else
      return pljson_printer.pretty_print_any(json_part); --escapes a possible internal string
    end if;
  end pp;

  procedure pp(obj pljson, v_path varchar2) as --using dbms_output.put_line
  begin
    dbms_output.put_line(pp(obj, v_path));
  end pp;

  -- spaces = false!
  procedure pp_htp(obj pljson, v_path varchar2) as --using htp.print
    json_part pljson_element;
  begin
    json_part := pljson_ext.get_json_element(obj, v_path);
    if (json_part is null) then
      htp.print;
    else
      htp.print(pljson_printer.pretty_print_any(json_part, false));
    end if;
  end pp_htp;

  function base64(binarydata blob) return pljson_list as
    obj pljson_list := pljson_list();
    c clob;

    v_clob_offset number := 1;
    v_lang_context number := dbms_lob.DEFAULT_LANG_CTX;
    v_amount number;
  begin
    --dbms_lob.createtemporary(c, false, dbms_lob.call);
    c := encodeBase64Blob2Clob(binarydata);
    v_amount := dbms_lob.getlength(c);
    v_clob_offset := 1;
    --dbms_output.put_line('v_amount: '||v_amount);
    while (v_clob_offset < v_amount) loop
      --dbms_output.put_line(v_offset);
      --temp := ;
      --dbms_output.put_line('size: '||length(temp));
      obj.append(dbms_lob.substr(c, 4000, v_clob_offset));
      v_clob_offset := v_clob_offset + 4000;
    end loop;
    dbms_lob.freetemporary(c);
    --dbms_output.put_line(obj.count);
    --dbms_output.put_line(obj.get_last().to_char);
    return obj;

  end base64;

  function base64(l pljson_list) return blob as
    c clob;
    b_ret blob;

    v_lang_context number := dbms_lob.DEFAULT_LANG_CTX;
    -- v_amount number;
  begin
    dbms_lob.createtemporary(c, false, dbms_lob.call);
    for i in 1 .. l.count loop
      dbms_lob.append(c, l.get(i).get_string());
    end loop;
    -- v_amount := dbms_lob.getlength(c);
    -- dbms_output.put_line('L C'||v_amount);
    b_ret := decodeBase64Clob2Blob(c);
    dbms_lob.freetemporary(c);
    return b_ret;
  end base64;

  function encode(binarydata blob) return pljson_string as
    obj pljson_string;
    c clob;
    v_lang_context number := dbms_lob.DEFAULT_LANG_CTX;
  begin
    dbms_lob.createtemporary(c, false, dbms_lob.call);
    c := encodeBase64Blob2Clob(binarydata);
    obj := pljson_string(c);

    --dbms_output.put_line(obj.count);
    --dbms_output.put_line(obj.get_last().to_char);
    /* dbms_lob.freetemporary(c); */
    return obj;
  end encode;

  function decode(v pljson_string) return blob as
    c clob;
    b_ret blob;

    v_lang_context number := dbms_lob.DEFAULT_LANG_CTX;
    -- v_amount number;
  begin
    /*
    dbms_lob.createtemporary(c, false, dbms_lob.call);
    v.get_string(c);
    */
    c := v.get_clob();
    -- v_amount := dbms_lob.getlength(c);
    -- dbms_output.put_line('L C'||v_amount);
    b_ret := decodeBase64Clob2Blob(c);
    /* dbms_lob.freetemporary(c); */
    return b_ret;

  end decode;

  procedure blob2clob(b blob, c out clob, charset varchar2 default 'UTF8') as
    v_dest_offset number := 1;
    v_src_offset number := 1;
    v_lang_context number := dbms_lob.DEFAULT_LANG_CTX;
    v_warning number := dbms_lob.NO_WARNING;
  begin
    dbms_lob.createtemporary(c, false, dbms_lob.call);
    dbms_lob.converttoclob(
      dest_lob => c,
      src_blob => b,
      amount => dbms_lob.LOBMAXSIZE,
      dest_offset => v_dest_offset,
      src_offset => v_src_offset,
      blob_csid => nls_charset_id(charset),
      lang_context => v_lang_context,
      warning => v_warning);
  end;
end pljson_ext;
/
show err

-- --- pljson_element.type.impl (body) ---
create or replace type body pljson_element as

  constructor function pljson_element return self as result as
  begin
    raise_application_error(-20000, 'pljson_element is not instantiable');
  end;

  /* all the is_ methods can
    test against typeval or
    return false and be overriden in children
  */
  member function is_object return boolean as
  begin
    return false;
  end;

  member function is_array return boolean as
  begin
    return false;
  end;

  member function is_string return boolean as
  begin
    return false;
  end;

  member function is_number return boolean as
  begin
    return false;
  end;

  member function is_bool return boolean as
  begin
    return false;
  end;

  member function is_null return boolean as
  begin
    return false;
  end;

  member function get_type return varchar2 as
  begin
    case self.typeval
      when 1 then return 'object';
      when 2 then return 'array';
      when 3 then return 'string';
      when 4 then return 'number';
      when 5 then return 'bool';
      when 6 then return 'null';
    end case;

    return 'unknown type';
  end;

  member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    raise_application_error(-20002, 'value_of() method should be overriden');
  end;

  /*
    member methods to remove need for treat()
  */
  member function get_string(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    raise_application_error(-20003, 'get_string() method is not supported by object of type:'  || get_type());
  end;

  member function get_clob return clob as
  begin
    raise_application_error(-20004, 'get_clob() method is not supported by object of type:'  || get_type());
  end;

  member function get_number return number as
  begin
    raise_application_error(-20005, 'get_number() method is not supported by object of type:'  || get_type());
  end;

  member function get_double return binary_double as
  begin
    raise_application_error(-20006, 'get_double() method is not supported by object of type:'  || get_type());
  end;

  member function is_number_repr_number return boolean as
  begin
    raise_application_error(-20008, 'is_number_repr_number() method is not supported by object of type:'  || get_type());
  end;

  member function is_number_repr_double return boolean as
  begin
    raise_application_error(-20009, 'is_number_repr_double() method is not supported by object of type:'  || get_type());
  end;

  member function get_bool return boolean as
  begin
    raise_application_error(-20007, 'get_bool() method is not supported by object of type:'  || get_type());
  end;

  member function count return number as
  begin
    raise_application_error(-20012, 'count() method is not supported by object of type:'  || get_type());
  end;

  member function get(pair_name varchar2) return pljson_element as
  begin
    raise_application_error(-20020, 'get(name) method is not supported by object of type:'  || get_type());
  end;

  member function get(position pls_integer) return pljson_element as
  begin
    raise_application_error(-20021, 'get(position) method is not supported by object of type:'  || get_type());
  end;

  member function path(json_path varchar2, base number default 1) return pljson_element as
  begin
    raise_application_error(-20010, 'path() method is not supported by object of type:'  || get_type());
  end;

  /* output methods */
  member function to_char(spaces boolean default true, chars_per_line number default 0) return varchar2 as
  begin
    if (spaces is null) then
      return pljson_printer.pretty_print_any(self, line_length => chars_per_line);
    else
      return pljson_printer.pretty_print_any(self, spaces, line_length => chars_per_line);
    end if;
  end;

  member procedure to_clob(self in pljson_element, buf in out nocopy clob, spaces boolean default false, chars_per_line number default 0, erase_clob boolean default true) as
  begin
    if (spaces is null) then
      pljson_printer.pretty_print_any(self, false, buf, line_length => chars_per_line, erase_clob => erase_clob);
    else
      pljson_printer.pretty_print_any(self, spaces, buf, line_length => chars_per_line, erase_clob => erase_clob);
    end if;
  end;

  member procedure print(self in pljson_element, spaces boolean default true, chars_per_line number default 8192, jsonp varchar2 default null) as --32512 is the real maximum in sqldeveloper
    my_clob clob;
  begin
    dbms_lob.createtemporary(my_clob, true);
    pljson_printer.pretty_print_any(self, spaces, my_clob, case when (chars_per_line>32512) then 32512 else chars_per_line end);
    pljson_printer.dbms_output_clob(my_clob, pljson_printer.newline_char, jsonp);
    dbms_lob.freetemporary(my_clob);
  end;

  member procedure htp(self in pljson_element, spaces boolean default false, chars_per_line number default 0, jsonp varchar2 default null) as
    my_clob clob;
  begin
    dbms_lob.createtemporary(my_clob, true);
    pljson_printer.pretty_print_any(self, spaces, my_clob, chars_per_line);
    pljson_printer.htp_output_clob(my_clob, jsonp);
    dbms_lob.freetemporary(my_clob);
  end;

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  member procedure get_internal_path(self in pljson_element, path pljson_path, path_position pls_integer, ret out nocopy pljson_element) as
  begin
    raise_application_error(-20010, 'path() method is not supported by object of type:'  || get_type());
  end;

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  member function put_internal_path(self in out nocopy pljson_element, path pljson_path, elem pljson_element, path_position pls_integer) return boolean as
  begin
    return false;
  end;
end;
/
show err

-- --- pljson_list.type.impl (body) ---
create or replace type body pljson_list as

  /* constructors */
  constructor function pljson_list return self as result as
  begin
    self.list_data := pljson_element_array();
    self.typeval := 2;

    --self.object_id := pljson_object_cache.next_id;
    return;
  end;

  constructor function pljson_list(str varchar2) return self as result as
  begin
    self := pljson_parser.parse_list(str);
    --self.typeval := 2;
    return;
  end;

  constructor function pljson_list(str clob) return self as result as
  begin
    self := pljson_parser.parse_list(str);
    --self.typeval := 2;
    return;
  end;

  constructor function pljson_list(str blob, charset varchar2 default 'UTF8') return self as result as
    c_str clob;
  begin
    pljson_ext.blob2clob(str, c_str, charset);
    self := pljson_parser.parse_list(c_str);
    --self.typeval := 2;
    return;
  end;

  constructor function pljson_list(str_array pljson_varray) return self as result as
  begin
    self.list_data := pljson_element_array();
    self.typeval := 2;
    for i in str_array.FIRST .. str_array.LAST loop
      append(str_array(i));
    end loop;

    --self.object_id := pljson_object_cache.next_id;
    return;
  end;

  constructor function pljson_list(num_array pljson_narray) return self as result as
  begin
    self.list_data := pljson_element_array();
    self.typeval := 2;
    for i in num_array.FIRST .. num_array.LAST loop
      append(num_array(i));
    end loop;

    --self.object_id := pljson_object_cache.next_id;
    return;
  end;

  constructor function pljson_list(elem pljson_element) return self as result as
  begin
    self := treat(elem as pljson_list);
    --self.typeval := 2;
    return;
  end;

  constructor function pljson_list(elem_array pljson_element_array) return self as result as
  begin
    self.list_data := elem_array;
    self.typeval := 2;
    return;
  end;

  overriding member function is_array return boolean as
  begin
    return true;
  end;

  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    return 'json array';
  end;

  /* list management */
  member procedure append(self in out nocopy pljson_list, elem pljson_element, position pls_integer default null) as
    indx pls_integer;
    --insert_value pljson_element;
  begin
    /*
    insert_value := elem;
    if insert_value is null then
      insert_value := pljson_null();
    end if;
    */
    if (position is null or position > self.count) then --end of list
      indx := self.count + 1;
      self.list_data.extend(1);
    elsif (position < 1) then --new first
      indx := self.count;
      self.list_data.extend(1);
      for x in reverse 1 .. indx loop
        self.list_data(x+1) := self.list_data(x);
      end loop;
      indx := 1;
      --self.list_data(1) := insert_value;
    else
      indx := self.count;
      self.list_data.extend(1);
      for x in reverse position .. indx loop
        self.list_data(x+1) := self.list_data(x);
      end loop;
      indx := position;
      --self.list_data(position) := insert_value;
    end if;

    if elem is not null then
      self.list_data(indx) := elem;
    else
      self.list_data(indx) := pljson_null();
    end if;
  end;

  member procedure append(self in out nocopy pljson_list, elem varchar2, position pls_integer default null) as
  begin
    if (elem is null and pljson_parser.empty_string_as_null) then
      append(pljson_null(), position);
    else
      append(pljson_string(elem), position);
    end if;
  end;

  member procedure append(self in out nocopy pljson_list, elem clob, position pls_integer default null) as
  begin
    if (elem is null) then
      append(pljson_null(), position);
    else
      append(pljson_string(elem), position);
    end if;
  end;

  member procedure append(self in out nocopy pljson_list, elem number, position pls_integer default null) as
  begin
    if (elem is null) then
      append(pljson_null(), position);
    else
      append(pljson_number(elem), position);
    end if;
  end;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure append(self in out nocopy pljson_list, elem binary_double, position pls_integer default null) as
  begin
    if (elem is null) then
      append(pljson_null(), position);
    else
      append(pljson_number(elem), position);
    end if;
  end;

  member procedure append(self in out nocopy pljson_list, elem boolean, position pls_integer default null) as
  begin
    if (elem is null) then
      append(pljson_null(), position);
    else
      append(pljson_bool(elem), position);
    end if;
  end;

  member procedure append(self in out nocopy pljson_list, elem pljson_list, position pls_integer default null) as
  begin
    if (elem is null) then
      append(pljson_null(), position);
    else
      append(treat(elem as pljson_element), position);
    end if;
  end;

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem pljson_element) as
    insert_value pljson_element;
    indx number;
  begin
    insert_value := elem;
    if insert_value is null then
      insert_value := pljson_null();
    end if;
    if (position > self.count) then --end of list
      indx := self.count + 1;
      self.list_data.extend(1);
      self.list_data(indx) := insert_value;
    elsif (position < 1) then --maybe an error message here
      null;
    else
      self.list_data(position) := insert_value;
    end if;
  end;

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem varchar2) as
  begin
    if (elem is null and pljson_parser.empty_string_as_null) then
      replace(position, pljson_null());
    else
      replace(position, pljson_string(elem));
    end if;
  end;

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem clob) as
  begin
    if (elem is null) then
      replace(position, pljson_null());
    else
      replace(position, pljson_string(elem));
    end if;
  end;

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem number) as
  begin
    if (elem is null) then
      replace(position, pljson_null());
    else
      replace(position, pljson_number(elem));
    end if;
  end;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem binary_double) as
  begin
    if (elem is null) then
      replace(position, pljson_null());
    else
      replace(position, pljson_number(elem));
    end if;
  end;

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem boolean) as
  begin
    if (elem is null) then
      replace(position, pljson_null());
    else
      replace(position, pljson_bool(elem));
    end if;
  end;

  member procedure replace(self in out nocopy pljson_list, position pls_integer, elem pljson_list) as
  begin
    if (elem is null) then
      replace(position, pljson_null());
    else
      replace(position, treat(elem as pljson_element));
    end if;
  end;

  member procedure remove(self in out nocopy pljson_list, position pls_integer) as
  begin
    if (position is null or position < 1 or position > self.count) then return; end if;
    for x in (position+1) .. self.count loop
      self.list_data(x-1) := self.list_data(x);
    end loop;
    self.list_data.trim(1);
  end;

  member procedure remove_first(self in out nocopy pljson_list) as
  begin
    for x in 2 .. self.count loop
      self.list_data(x-1) := self.list_data(x);
    end loop;
    if (self.count > 0) then
      self.list_data.trim(1);
    end if;
  end;

  member procedure remove_last(self in out nocopy pljson_list) as
  begin
    if (self.count > 0) then
      self.list_data.trim(1);
    end if;
  end;

  overriding member function count return number as
  begin
    return self.list_data.count;
  end;

  overriding member function get(position pls_integer) return pljson_element as
  begin
    if (self.count >= position and position > 0) then
      return self.list_data(position);
    end if;
    return null; -- do not throw error, just return null
  end;

  member function get_string(position pls_integer) return varchar2 as
    elem pljson_element := get(position);
  begin
    if elem is not null and elem is of (pljson_string) then
      return elem.get_string();
    end if;
    return null;
  end;

  member function get_clob(position pls_integer) return clob as
    elem pljson_element := get(position);
  begin
    if elem is not null and elem is of (pljson_string) then
      return elem.get_clob();
    end if;
    return null;
  end;

  member function get_number(position pls_integer) return number as
    elem pljson_element := get(position);
  begin
    if elem is not null and elem is of (pljson_number) then
      return elem.get_number();
    end if;
    return null;
  end;

  member function get_double(position pls_integer) return binary_double as
    elem pljson_element := get(position);
  begin
    if elem is not null and elem is of (pljson_number) then
      return elem.get_double();
    end if;
    return null;
  end;

  member function get_bool(position pls_integer) return boolean as
    elem pljson_element := get(position);
  begin
    if elem is not null and elem is of (pljson_bool) then
      return elem.get_bool();
    end if;
    return null;
  end;

  member function get_pljson_list(position pls_integer) return pljson_list as
    elem pljson_element := get(position);
  begin
    if elem is not null and elem is of (pljson_list) then
      return treat(elem as pljson_list);
    end if;
    return null;
  end;

  member function head return pljson_element as
  begin
    if (self.count > 0) then
      return self.list_data(self.list_data.first);
    end if;
    return null; -- do not throw error, just return null
  end;

  member function last return pljson_element as
  begin
    if (self.count > 0) then
      return self.list_data(self.list_data.last);
    end if;
    return null; -- do not throw error, just return null
  end;

  member function tail return pljson_list as
    t pljson_list;
  begin
    if (self.count > 0) then
      t := self; --pljson_list(self);
      t.remove(1);
      return t;
    else
      return pljson_list();
    end if;
  end;

  /* json path */
  overriding member function path(json_path varchar2, base number default 1) return pljson_element as
    cp pljson_list := self;
  begin
    return pljson_ext.get_json_element(pljson(cp), json_path, base);
  end path;

  /* json path_put */
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem pljson_element, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    pljson_ext.put(objlist, json_path, elem, base);
    self := objlist.get_values;
  end path_put;

  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem varchar2, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    if (elem is null and pljson_parser.empty_string_as_null) then
      pljson_ext.put(objlist, json_path, pljson_null(), base);
    else
      pljson_ext.put(objlist, json_path, elem, base);
    end if;
    self := objlist.get_values;
  end path_put;

  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem clob, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    if (elem is null) then
      pljson_ext.put(objlist, json_path, pljson_null(), base);
    else
      pljson_ext.put(objlist, json_path, elem, base);
    end if;
    self := objlist.get_values;
  end path_put;

  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem number, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    if (elem is null) then
      pljson_ext.put(objlist, json_path, pljson_null(), base);
    else
      pljson_ext.put(objlist, json_path, elem, base);
    end if;
    self := objlist.get_values;
  end path_put;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem binary_double, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    if (elem is null) then
      pljson_ext.put(objlist, json_path, pljson_null(), base);
    else
      pljson_ext.put(objlist, json_path, elem, base);
    end if;
    self := objlist.get_values;
  end path_put;

  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem boolean, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    if (elem is null) then
      pljson_ext.put(objlist, json_path, pljson_null(), base);
    else
      pljson_ext.put(objlist, json_path, elem, base);
    end if;
    self := objlist.get_values;
  end path_put;

  member procedure path_put(self in out nocopy pljson_list, json_path varchar2, elem pljson_list, base number default 1) as
    objlist pljson;
    jp pljson_list := pljson_ext.parsePath(json_path, base);
  begin
    while (jp.head().get_number() > self.count) loop
      self.append(pljson_null());
    end loop;

    objlist := pljson(self);
    if (elem is null) then
      pljson_ext.put(objlist, json_path, pljson_null(), base);
    else
      pljson_ext.put(objlist, json_path, elem, base);
    end if;
    self := objlist.get_values;
  end path_put;

  /* json path_remove */
  member procedure path_remove(self in out nocopy pljson_list, json_path varchar2, base number default 1) as
    objlist pljson := pljson(self);
  begin
    pljson_ext.remove(objlist, json_path, base);
    self := objlist.get_values;
  end path_remove;

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member procedure get_internal_path(self in pljson_list, path pljson_path, path_position pls_integer, ret out nocopy pljson_element) as
    indx pls_integer := path(path_position).indx;
  begin
    if (indx <= self.list_data.count) then
      if (path_position < path.count) then
        self.list_data(indx).get_internal_path(path, path_position + 1, ret);
      else
        ret := self.list_data(indx);
      end if;
    end if;
  end;

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member function put_internal_path(self in out nocopy pljson_list, path pljson_path, elem pljson_element, path_position pls_integer) return boolean as
    indx pls_integer := path(path_position).indx;
  begin
    indx := path(path_position).indx;
    if (indx > self.list_data.count) then
      if (elem is null) then
        return false;
      end if;

      self.list_data.extend;
      self.list_data(self.list_data.count) := pljson_null();

      if (indx > self.list_data.count) then
        self.list_data.extend(indx - self.list_data.count, self.list_data.count);
      end if;
    end if;

    if (path_position < path.count) then
      if (path(path_position + 1).indx is null) then
        if (not self.list_data(indx).is_object()) then
          if (elem is not null) then
            self.list_data(indx) := pljson();
          else
            return false;
          end if;
        end if;
      else
        if (not self.list_data(indx).is_object() and not self.list_data(indx).is_array()) then
          if (elem is not null) then
            self.list_data(indx) := pljson_list();
          else
            return false;
          end if;
        end if;
      end if;

      if (self.list_data(indx).put_internal_path(path, elem, path_position + 1)) then
        self.remove(indx);
        return self.list_data.count = 0;
      end if;
    else
      if (elem is not null) then
        self.list_data(indx) := elem;
      else
        self.remove(indx);
        return self.list_data.count = 0;
      end if;
    end if;

    return false;
  end;
end;
/
show err

-- --- pljson.type.impl (body) ---
create or replace type body pljson as

  /* constructors */
  constructor function pljson return self as result as
  begin
    self.json_data := pljson_element_array();
    self.typeval := 1;
    self.check_for_duplicate := 1;

    --self.object_id := pljson_object_cache.next_id;
    return;
  end;

  constructor function pljson(str varchar2) return self as result as
  begin
    self := pljson_parser.parser(str);
    --self.typeval := 1;
    self.check_for_duplicate := 1;
    return;
  end;

  constructor function pljson(str in clob) return self as result as
  begin
    self := pljson_parser.parser(str);
    --self.typeval := 1;
    self.check_for_duplicate := 1;
    return;
  end;

  constructor function pljson(str in blob, charset varchar2 default 'UTF8') return self as result as
    c_str clob;
  begin
    pljson_ext.blob2clob(str, c_str, charset);
    self := pljson_parser.parser(c_str);
    --self.typeval := 1;
    self.check_for_duplicate := 1;
    return;
  end;

  constructor function pljson(str_array pljson_varray) return self as result as
    new_pair boolean := True;
    pair_name varchar2(32767);
    pair_value varchar2(32767);
  begin
    self.typeval := 1;
    self.check_for_duplicate := 1;
    self.json_data := pljson_element_array();
    for i in str_array.FIRST .. str_array.LAST loop
      if new_pair then
        pair_name := str_array(i);
        new_pair := False;
      else
        pair_value := str_array(i);
        put(pair_name, pair_value);
        new_pair := True;
      end if;
    end loop;

    --self.object_id := pljson_object_cache.next_id;
    return;
  end;

  constructor function pljson(elem pljson_element) return self as result as
  begin
    self := treat(elem as pljson);
    --self.typeval := 1;
    self.check_for_duplicate := 1;
    return;
  end;

  constructor function pljson(l pljson_list) return self as result as
  begin
    self.typeval := 1;
    self.check_for_duplicate := 1;
    self.json_data := pljson_element_array();
    for i in 1 .. l.list_data.count loop
      self.json_data.extend(1);
      self.json_data(i) := l.list_data(i);
      if (l.list_data(i).mapname is null or l.list_data(i).mapname like 'row%') then
        self.json_data(i).mapname := 'row'||i;
      end if;
      self.json_data(i).mapindx := i;
    end loop;

    --self.object_id := pljson_object_cache.next_id;
    return;
  end;

  overriding member function is_object return boolean as
  begin
    return true;
  end;

  overriding member function value_of(max_byte_size number default null, max_char_size number default null) return varchar2 as
  begin
    return 'json object';
  end;

  /* member management */
  member procedure remove(self in out nocopy pljson, pair_name varchar2) as
    temp pljson_element;
    indx pls_integer;
  begin
    temp := get(pair_name);
    if (temp is null) then return; end if;

    indx := json_data.next(temp.mapindx);
    loop
      exit when indx is null;
      json_data(indx).mapindx := indx - 1;
      json_data(indx-1) := json_data(indx);
      indx := json_data.next(indx);
    end loop;
    json_data.trim(1);
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value pljson_element, position pls_integer default null) as
    insert_value pljson_element;
    indx pls_integer; x number;
    temp pljson_element;
  begin
    --dbms_output.put_line('name: ' || pair_name);

    --if (pair_name is null) then
    --  raise_application_error(-20102, 'JSON put-method type error: name cannot be null');
    --end if;
    insert_value := pair_value;
    if insert_value is null then
      insert_value := pljson_null();
    end if;
    insert_value.mapname := pair_name;
    if (self.check_for_duplicate = 1) then temp := get(pair_name); else temp := null; end if;
    if (temp is not null) then
      insert_value.mapindx := temp.mapindx;
      json_data(temp.mapindx) := insert_value;
      return;
    elsif (position is null or position > self.count) then
      --insert at the end of the list
      --dbms_output.put_line('insert end');
      json_data.extend(1);
      /* changed to common style of updating mapindx; fix bug in assignment order */
      insert_value.mapindx := json_data.count;
      json_data(json_data.count) := insert_value;
      --dbms_output.put_line('mapindx: ' || insert_value.mapindx);
      --dbms_output.put_line('mapname: ' || insert_value.mapname);
    elsif (position < 2) then
      --insert at the start of the list
      indx := json_data.last;
      json_data.extend;
      loop
        exit when indx is null;
        temp := json_data(indx);
        temp.mapindx := indx+1;
        json_data(temp.mapindx) := temp;
        indx := json_data.prior(indx);
      end loop;
      /* changed to common style of updating mapindx; fix bug in assignment order */
      insert_value.mapindx := 1;
      json_data(1) := insert_value;
    else
      --insert somewhere in the list
      indx := json_data.last;
      --dbms_output.put_line('indx: ' || indx);
      json_data.extend;
      loop
        --dbms_output.put_line('indx: ' || indx);
        temp := json_data(indx);
        temp.mapindx := indx + 1;
        json_data(temp.mapindx) := temp;
        exit when indx = position;
        indx := json_data.prior(indx);
      end loop;
      /* changed to common style of updating mapindx; fix bug in assignment order */
      insert_value.mapindx := position;
      json_data(position) := insert_value;
    end if;
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value varchar2, position pls_integer default null) as
  begin
    if (pair_value is null and pljson_parser.empty_string_as_null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, pljson_string(pair_value), position);
    end if;
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value clob, position pls_integer default null) as
  begin
    if (pair_value is null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, pljson_string(pair_value), position);
    end if;
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value number, position pls_integer default null) as
  begin
    if (pair_value is null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, pljson_number(pair_value), position);
    end if;
  end;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value binary_double, position pls_integer default null) as
  begin
    if (pair_value is null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, pljson_number(pair_value), position);
    end if;
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value boolean, position pls_integer default null) as
  begin
    if (pair_value is null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, pljson_bool(pair_value), position);
    end if;
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value pljson, position pls_integer default null) as
  begin
    if (pair_value is null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, treat(pair_value as pljson_element), position);
    end if;
  end;

  member procedure put(self in out nocopy pljson, pair_name varchar2, pair_value pljson_list, position pls_integer default null) as
  begin
    if (pair_value is null) then
      put(pair_name, pljson_null(), position);
    else
      put(pair_name, treat(pair_value as pljson_element), position);
    end if;
  end;

  overriding member function count return number as
  begin
    return self.json_data.count;
  end;

  overriding member function get(pair_name varchar2) return pljson_element as
    indx pls_integer;
  begin
    indx := json_data.first;
    loop
      exit when indx is null;
      if (pair_name is null and json_data(indx).mapname is null) then return json_data(indx); end if;
      if (json_data(indx).mapname = pair_name) then return json_data(indx); end if;
      indx := json_data.next(indx);
    end loop;
    return null;
  end;

  member function get_string(pair_name varchar2) return varchar2 as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson_string) then
      return elem.get_string();
    end if;
    return null;
  end;

  member function get_clob(pair_name varchar2) return clob as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson_string) then
      return elem.get_clob();
    end if;
    return null;
  end;

  member function get_number(pair_name varchar2) return number as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson_number) then
      return elem.get_number();
    end if;
    return null;
  end;

  member function get_double(pair_name varchar2) return binary_double as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson_number) then
      return elem.get_double();
    end if;
    return null;
  end;

  member function get_bool(pair_name varchar2) return boolean as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson_bool) then
      return elem.get_bool();
    end if;
    return null;
  end;

  member function get_pljson(pair_name varchar2) return pljson as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson) then
      return treat(elem as pljson);
    end if;
    return null;
  end;

  member function get_pljson_list(pair_name varchar2) return pljson_list as
    elem pljson_element := get(pair_name);
  begin
    if elem is not null and elem is of (pljson_list) then
      return treat(elem as pljson_list);
    end if;
    return null;
  end;

  overriding member function get(position pls_integer) return pljson_element as
  begin
    if (self.count >= position and position > 0) then
      return self.json_data(position);
    end if;
    return null; -- do not throw error, just return null
  end;

  member function index_of(pair_name varchar2) return number as
    indx pls_integer;
  begin
    indx := json_data.first;
    loop
      exit when indx is null;
      if (pair_name is null and json_data(indx).mapname is null) then return indx; end if;
      if (json_data(indx).mapname = pair_name) then return indx; end if;
      indx := json_data.next(indx);
    end loop;
    return -1;
  end;

  member function exist(pair_name varchar2) return boolean as
  begin
    return (get(pair_name) is not null);
  end;

  member procedure check_duplicate(self in out nocopy pljson, v_set boolean) as
  begin
    if (v_set) then
      check_for_duplicate := 1;
    else
      check_for_duplicate := 0;
    end if;
  end;

  member procedure remove_duplicates(self in out nocopy pljson) as
  begin
    pljson_parser.remove_duplicates(self);
  end remove_duplicates;

  /* json path */
  overriding member function path(json_path varchar2, base number default 1) return pljson_element as
  begin
    return pljson_ext.get_json_element(self, json_path, base);
  end path;

  /* json path_put */
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem pljson_element, base number default 1) as
  begin
    pljson_ext.put(self, json_path, elem, base);
  end path_put;

  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem varchar2, base number default 1) as
  begin
    if (elem is null and pljson_parser.empty_string_as_null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem clob, base number default 1) as
  begin
    if (elem is null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem number, base number default 1) as
  begin
    if (elem is null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  /* E.I.Sarmas (github.com/dsnz)   2016-12-01   support for binary_double numbers */
  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem binary_double, base number default 1) as
  begin
    if (elem is null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem boolean, base number default 1) as
  begin
    if (elem is null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem pljson_list, base number default 1) as
  begin
    if (elem is null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  member procedure path_put(self in out nocopy pljson, json_path varchar2, elem pljson, base number default 1) as
  begin
    if (elem is null) then
      pljson_ext.put(self, json_path, pljson_null(), base);
    else
      pljson_ext.put(self, json_path, elem, base);
    end if;
  end path_put;

  member procedure path_remove(self in out nocopy pljson, json_path varchar2, base number default 1) as
  begin
    pljson_ext.remove(self, json_path, base);
  end path_remove;

  /* Thanks to Matt Nolan */
  member function get_keys return pljson_list as
    keys pljson_list;
    indx pls_integer;
  begin
    keys := pljson_list();
    indx := json_data.first;
    loop
      exit when indx is null;
      keys.append(json_data(indx).mapname);
      indx := json_data.next(indx);
    end loop;
    return keys;
  end;

  member function get_values return pljson_list as
    vals pljson_list := pljson_list();
  begin
    vals.list_data := self.json_data;
    return vals;
  end;

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member procedure get_internal_path(self in pljson, path pljson_path, path_position pls_integer, ret out nocopy pljson_element) as
    indx pls_integer := path(path_position).indx;
  begin

    if (indx is null) then
      indx := self.json_data.first;
      loop
        exit when indx is null;

        if ((path(path_position).name is null and self.json_data(indx).mapname is null) or
            (self.json_data(indx).mapname = path(path_position).name))
        then
          if (path_position < path.count) then
            self.json_data(indx).get_internal_path(path, path_position + 1, ret);
          else
            ret := self.json_data(indx);
          end if;

          exit;
        end if;

        indx := self.json_data.next(indx);
      end loop;
    else
      if (indx <= self.json_data.count) then
        if (path_position < path.count) then
          self.json_data(indx).get_internal_path(path, path_position + 1, ret);
        else
          ret := self.json_data(indx);
        end if;
      end if;
    end if;
  end;

  /* private method for internal use, not part of the API, contributed by @asfernandes */
  overriding member function put_internal_path(self in out nocopy pljson, path pljson_path, elem pljson_element, path_position pls_integer) return boolean as
    indx pls_integer;
    keystring varchar2(4000);
    new_obj pljson;
    new_list pljson_list;
    ret boolean := false;
  begin
    if (path(path_position).indx is null) then
      keystring := path(path_position).name;
    else
      if (path(path_position).indx > self.json_data.count) then
        if (elem is null) then
          return false;
        end if;
        raise_application_error(-20110, 'PLJSON_EXT put error: access object with too few members.');
      end if;

      keystring := self.json_data(path(path_position).indx).mapname;
    end if;

    indx := self.json_data.first;
    loop
      exit when indx is null;

      if ((keystring is null and self.json_data(indx).mapname is null) or (self.json_data(indx).mapname = keystring)) then
        if (path_position < path.count) then
          if (path(path_position + 1).indx is null) then
            if (not self.json_data(indx).is_object()) then
              if (elem is not null) then
                put(keystring, pljson());
              else
                return false;
              end if;
            end if;
          else
            if (not self.json_data(indx).is_object() and not self.json_data(indx).is_array()) then
              if (elem is not null) then
                put(keystring, pljson_list());
              else
                return false;
              end if;
            end if;
          end if;

          if (self.json_data(indx).put_internal_path(path, elem, path_position + 1)) then
            self.remove(keystring);
            return true;
          end if;
        else
          if (elem is null) then
            self.remove(keystring);
            return true;
          else
            self.put(keystring, elem);
          end if;
        end if;

        return false;
      end if;

      indx := self.json_data.next(indx);
    end loop;

    if (elem is not null) then
      if (path_position = path.count) then
        put(keystring, elem);
      else
        if (path(path_position + 1).indx is null) then
          new_obj := pljson();
          ret := new_obj.put_internal_path(path, elem, path_position + 1);
          put(keystring, new_obj);
        else
          new_list := pljson_list();
          ret := new_list.put_internal_path(path, elem, path_position + 1);
          put(keystring, new_list);
        end if;
      end if;
    end if;

    return ret;
  end;
end;
/
show err

-- ====================================================================
-- STEP 5: GRANT EXECUTE la PUBLIC
-- ====================================================================
PROMPT [5/6] Grant EXECUTE to PUBLIC...;

GRANT EXECUTE ON pljson_path_segment TO PUBLIC;
GRANT EXECUTE ON pljson_path TO PUBLIC;
GRANT EXECUTE ON pljson_element TO PUBLIC;
GRANT EXECUTE ON pljson_element_array TO PUBLIC;
GRANT EXECUTE ON pljson_varray TO PUBLIC;
GRANT EXECUTE ON pljson_narray TO PUBLIC;
GRANT EXECUTE ON pljson_list TO PUBLIC;
GRANT EXECUTE ON pljson TO PUBLIC;
GRANT EXECUTE ON pljson_string TO PUBLIC;
GRANT EXECUTE ON pljson_number TO PUBLIC;
GRANT EXECUTE ON pljson_bool TO PUBLIC;
GRANT EXECUTE ON pljson_null TO PUBLIC;
GRANT EXECUTE ON pljson_parser TO PUBLIC;
GRANT EXECUTE ON pljson_printer TO PUBLIC;
GRANT EXECUTE ON pljson_ext TO PUBLIC;

-- ====================================================================
-- STEP 6: PUBLIC SYNONYMS
-- ====================================================================
PROMPT [6/6] Creare PUBLIC SYNONYMS...;

CREATE PUBLIC SYNONYM pljson_path_segment FOR CONTAFIN_ORACLE.pljson_path_segment;
CREATE PUBLIC SYNONYM pljson_path FOR CONTAFIN_ORACLE.pljson_path;
CREATE PUBLIC SYNONYM pljson_element FOR CONTAFIN_ORACLE.pljson_element;
CREATE PUBLIC SYNONYM pljson_element_array FOR CONTAFIN_ORACLE.pljson_element_array;
CREATE PUBLIC SYNONYM pljson_varray FOR CONTAFIN_ORACLE.pljson_varray;
CREATE PUBLIC SYNONYM pljson_narray FOR CONTAFIN_ORACLE.pljson_narray;
CREATE PUBLIC SYNONYM pljson_list FOR CONTAFIN_ORACLE.pljson_list;
CREATE PUBLIC SYNONYM pljson FOR CONTAFIN_ORACLE.pljson;
CREATE PUBLIC SYNONYM pljson_string FOR CONTAFIN_ORACLE.pljson_string;
CREATE PUBLIC SYNONYM pljson_number FOR CONTAFIN_ORACLE.pljson_number;
CREATE PUBLIC SYNONYM pljson_bool FOR CONTAFIN_ORACLE.pljson_bool;
CREATE PUBLIC SYNONYM pljson_null FOR CONTAFIN_ORACLE.pljson_null;
CREATE PUBLIC SYNONYM pljson_parser FOR CONTAFIN_ORACLE.pljson_parser;
CREATE PUBLIC SYNONYM pljson_printer FOR CONTAFIN_ORACLE.pljson_printer;
CREATE PUBLIC SYNONYM pljson_ext FOR CONTAFIN_ORACLE.pljson_ext;

-- ====================================================================
-- Verificare
-- ====================================================================
PROMPT;
PROMPT Verificare obiecte create:;
SELECT object_name, object_type, status
  FROM user_objects
 WHERE object_name LIKE 'PLJSON%'
 ORDER BY object_type, object_name;

PROMPT;
PROMPT Test rapid:;
DECLARE
  v_arr  pljson_list;
  v_obj  pljson;
  v_sku  VARCHAR2(100);
BEGIN
  v_arr := pljson_list('[{"sku":"TEST1","price":"10.5"},{"sku":"TEST2","price":"25.0"}]');
  FOR i IN 1 .. v_arr.count LOOP
    v_obj := pljson(v_arr.get(i));
    v_sku := v_obj.get_string('sku');
    DBMS_OUTPUT.PUT_LINE('  Articol ' || i || ': SKU=' || v_sku || ', pret=' || v_obj.get_string('price'));
  END LOOP;
  DBMS_OUTPUT.PUT_LINE('PL/JSON instalat cu succes!');
END;
/

exec contafin_oracle.pack_migrare.UpdateVersiune('co_2026_03_16_01_COMUN_PLJSON');
commit;

PROMPT;
PROMPT =============================================;
PROMPT  Instalare PL/JSON completa!;
PROMPT =============================================;
PROMPT;