Bypass the JSON object using PL / SQL, and output all data in key value format in Oracle - sql

I have JSON, I want to bypass it and display all the data that is in it. But the problem is that I can't know what data will be there. I made the code, but it only looks for top-level objects. And the rest are not. I will be grateful for your help.
DECLARE
l_object json_object_t;
l_key_list json_key_list;
v_clob CLOB;
tv apex_json.t_values;
BEGIN
-- JSON_OBJECT can figure out what keys it has...
v_clob := '{"devices":{"id":"d652f632-0835-871b-a140-58701f019000","scale_id":"88348A32BD3D149FE055000000000001"},"data":{"external_id":"40023"},"data_weight":{"weight":"20322","prevWeight":"1000","prevTransaction":"1607680754361","transaction":"1607680754361","on":"false","transactionDataCount":"1","stable":"false","duration":"12","transactionMaxWeight":"2000","perimetr":"true","driverInCar":"false"}}';
apex_json.parse(tv,v_clob);
l_object := json_object_t.parse (v_clob);
l_key_list := l_object.get_keys;
FOR counter IN 1 .. l_key_list.COUNT
LOOP
DBMS_OUTPUT.put_line (
l_key_list (counter)
|| ' : '
|| apex_json.get_varchar2(p_path => l_key_list (counter), p_values => tv));
END LOOP;
END;
If you take into account only the top level objects, then this code works
DECLARE
l_object json_object_t;
l_key_list json_key_list;
v_clob CLOB;
tv apex_json.t_values;
BEGIN
-- JSON_OBJECT can figure out what keys it has...
v_clob := '{"devices":"3423","data":"okwwwe"}';
apex_json.parse(tv,v_clob);
l_object := json_object_t.parse (v_clob);
l_key_list := l_object.get_keys;
FOR counter IN 1 .. l_key_list.COUNT
LOOP
DBMS_OUTPUT.put_line (
l_key_list (counter)
|| ' : '
|| apex_json.get_varchar2(p_path => l_key_list (counter), p_values => tv));
END LOOP;
END;
RESULT :
devices : 3423
data : okwwwe
And how to deduce the data from the first example, I can not understand. These data must correspond to the hierarchy.

If you have access to the native JSON types (JSON_ELEMENT_T, JSON_ARRAY_T, etc.) you should always use them over using the APEX_JSON package ans they will have much better performance. A package similar to the one below can be used to output the full hierarchy of a JSON object or array. Feel free to tweak the package to change the format of the output.
Package Specification
CREATE OR REPLACE PACKAGE output_json
AS
c_padding_char CONSTANT VARCHAR2 (1) := '-';
c_padding_amount CONSTANT PLS_INTEGER := 1;
PROCEDURE output_scalar (p_element json_element_t,
p_padding PLS_INTEGER,
p_json_key VARCHAR2 DEFAULT NULL);
PROCEDURE output_object (p_object json_object_t, p_padding PLS_INTEGER);
PROCEDURE output_array (p_array json_array_t, p_padding PLS_INTEGER);
PROCEDURE output_element (p_element json_element_t,
p_padding PLS_INTEGER,
p_json_key VARCHAR2 DEFAULT NULL);
END;
/
Package Body
CREATE OR REPLACE PACKAGE BODY output_json
AS
PROCEDURE output_scalar (p_element json_element_t,
p_padding PLS_INTEGER,
p_json_key VARCHAR2 DEFAULT NULL)
IS
BEGIN
DBMS_OUTPUT.put_line (
LPAD (c_padding_char, p_padding, c_padding_char)
|| CASE WHEN p_json_key IS NOT NULL THEN p_json_key || ' : ' END
|| CASE
WHEN p_element.is_boolean
THEN
CASE WHEN p_element.to_boolean THEN 'TRUE' ELSE 'FALSE' END || ' (boolean)'
WHEN p_element.is_date
THEN
TO_CHAR (p_element.TO_DATE, 'YYYY-MM-DD') || ' (date)'
WHEN p_element.is_number
THEN
p_element.TO_NUMBER || ' (number)'
WHEN p_element.is_string
THEN
p_element.TO_STRING || ' (string)'
END);
END;
PROCEDURE output_object (p_object json_object_t, p_padding PLS_INTEGER)
IS
l_keys json_key_list;
l_element json_element_t;
BEGIN
l_keys := p_object.get_keys;
FOR i IN 1 .. l_keys.COUNT
LOOP
l_element := p_object.get (l_keys (i));
output_element (l_element, p_padding, l_keys (i));
END LOOP;
END;
PROCEDURE output_array (p_array json_array_t, p_padding PLS_INTEGER)
IS
BEGIN
FOR i IN 0 .. p_array.get_size - 1
LOOP
output_element (p_array.get (i), p_padding);
END LOOP;
END;
PROCEDURE output_element (p_element json_element_t,
p_padding PLS_INTEGER,
p_json_key VARCHAR2 DEFAULT NULL)
IS
BEGIN
DBMS_OUTPUT.put_line (
CASE
WHEN p_json_key IS NOT NULL AND NOT p_element.is_scalar THEN p_json_key || ' : '
END);
IF p_element.is_scalar
THEN
output_scalar (p_element, p_padding, p_json_key);
ELSIF p_element.is_object
THEN
output_object (TREAT (p_element AS json_object_t), p_padding + c_padding_amount);
ELSIF p_element.is_array
THEN
output_array (TREAT (p_element AS json_array_t), p_padding + c_padding_amount);
END IF;
END;
END;
/
Example Call
DECLARE
l_clob CLOB;
BEGIN
l_clob :=
'{"arr":[1,2,3],"devices":{"id":"d652f632-0835-871b-a140-58701f019000","scale_id":"88348A32BD3D149FE055000000000001"},"data":{"external_id":"40023"},"data_weight":{"weight":"20322","prevWeight":"1000","prevTransaction":"1607680754361","transaction":"1607680754361","on":"false","transactionDataCount":"1","stable":"false","duration":"12","transactionMaxWeight":"2000","perimetr":"true","driverInCar":"false"}}';
output_json.output_element (json_element_t.parse (l_clob), 0);
END;
/
Example Output
arr :
--1 (number)
--2 (number)
--3 (number)
devices :
--id : "d652f632-0835-871b-a140-58701f019000" (string)
--scale_id : "88348A32BD3D149FE055000000000001" (string)
data :
--external_id : "40023" (string)
data_weight :
--weight : "20322" (string)
--prevWeight : "1000" (string)
--prevTransaction : "1607680754361" (string)
--transaction : "1607680754361" (string)
--on : "false" (string)
--transactionDataCount : "1" (string)
--stable : "false" (string)
--duration : "12" (string)
--transactionMaxWeight : "2000" (string)
--perimetr : "true" (string)
--driverInCar : "false" (string)

I also made my own version, but it is limited to 2 levels of objects, and does not take into account arrays.
DECLARE
l_object json_object_t;
l_key_obj json_key_list;
v_clob CLOB;
tv apex_json.t_values;
obj_in_obj JSON_OBJECT_T;
l_key_list json_key_list;
BEGIN
v_clob := '{
"devices":{
"id":"d652f632-0835-871b-a140-58701f019000",
"scale_id":"88348A32BD3D149FE055000000000001"
},
"data":{
"external_id":"40023"
},
"data_weight":{
"weight":"20322",
"prevWeight":"1000",
"prevTransaction":"1607680754361",
"transaction":"1607680754361",
"on":"false",
"transactionDataCount":"1",
"stable":"false",
"duration":"12",
"transactionMaxWeight":"2000",
"perimetr":"true",
"driverInCar":"false"
}
}';
apex_json.parse(tv,v_clob);
l_object := json_object_t.parse (v_clob);
l_key_list := l_object.get_keys;
FOR counter IN 1 .. l_key_list.COUNT
LOOP
obj_in_obj := l_object.get_object(l_key_list (counter));
l_key_obj := obj_in_obj.get_keys;
FOR counter_all_obj IN 1 .. l_key_obj.COUNT
LOOP
DBMS_OUTPUT.put_line (
l_key_list (counter)||'.'||l_key_obj (counter_all_obj)
|| ' : '
|| apex_json.get_varchar2(p_path => l_key_list (counter)||'.'|| l_key_obj (counter_all_obj), p_values => tv));
END LOOP;
END LOOP;
END;

Related

catch the value of response headers and use it in another api call in pl sql

I would like to ask how can I loop the value of a return in 'url_next' variable into url varible so that I can use the function until the all users imported from the api call .
so the value returned from 'url_next'
into
url := 'https://dev-9466225.okta.com/api/v1/users';
l_http_request := utl_http.begin_request(apex_string.format( url , 'GET', 'HTTP/1.1'));
create or replace function OKTA_API_X
(url IN VARCHAR2)
return varchar2
IS
v_username VARCHAR2(50);
v_email VARCHAR2(50);
v_firstname VARCHAR2(50);
v_last_name VARCHAR2(50);
l_http_request utl_http.req;
l_http_response utl_http.resp;
l_text VARCHAR2(32767);
resp CLOB := '';
l_json_obj json_object_t;
l_json_in json_array_t;
name VARCHAR2(2560);
value VARCHAR2(2024);
url_next VARCHAR2(100);
url VARCHAR2(50);
BEGIN
url := 'https://dev-9466225.okta.com/api/v1/users';
l_http_request := utl_http.begin_request(apex_string.format( url , 'GET', 'HTTP/1.1'));
utl_http.set_wallet('file:c:/okta_wallet_base/', NULL);
utl_http.set_header(l_http_request, 'content-type', 'application/json');
utl_http.set_header(l_http_request, 'authorization', 'SSWS 00mz6_ost-rfegVu5K5I6dEs33JLIFSmcBO8VxzI');
utl_http.set_header(l_http_request, 'Accept', 'application/json');
l_http_response := utl_http.get_response(l_http_request);
-- dbms_output.put_line('HTTP response status code =>' || l_http_response.status_code);
-- dbms_output.put_line('HTTP response reason phrase => ' || l_http_response.reason_phrase);
BEGIN
LOOP
utl_http.read_text(l_http_response, l_text,32766)
resp := resp || l_text;
END LOOP;
END;
--- JASON PARSING
l_json_in := json_array_t(resp);
FOR indx IN 0..l_json_in.get_size - 1 LOOP
l_json_obj := TREAT(l_json_in.get(indx) AS json_object_t);
--GET JSON OBJECT AND STRINGS FROM RESPONSE
v_firstname := l_json_obj.get_object('profile').get_string('firstName');
v_last_name := l_json_obj.get_object('profile').get_string('lastName');
v_email := l_json_obj.get_object('profile').get_string('email');
v_username := l_json_obj.get_object('profile').get_string('login');
-------------------------------
dbms_output.put_line(v_firstname);
--dbms_output.put_line(v_EMAIL);
END LOOP;
---------------------------------------------------------------------------- FOR i IN 1..
utl_http.get_header_count(l_http_response) LOOP
utl_http.get_header(l_http_response, i, name, value);
IF
name = 'link'
AND value LIKE '%rel="next"'
THEN
url_next := ( trim(TRAILING '>' FROM regexp_substr(value, 'http[^>]+>')) );
END IF;
END LOOP;
BEGIN
WHILE url_next IS NOT NULL LOOP
IF url_next <> url THEN
url := url_next;
ELSE
NULL;
END IF;
END LOOP;
END;
EXCEPTION
WHEN OTHERS THEN
BEGIN
utl_http.end_response(l_http_response);
dbms_output.put_line('ERROR →' || sqlerrm);
END;
END;

How convert long text to UTF8 in Oracle 11g?

I have large text around 20000 I need convert to UTF8.
select convert('large text','UTF8') from dual
I get error:
ORA-01704: string literal too long
Try this get another error
declare
v_hugetxt varchar2(32767);
begin
v_hugetxt:='huge text'
select convert(v_hugetxt,'UTF8') into v_hugetxt
from dual;
end;
I get error
ORA-01460: unimplemented or unreasonable conversion requested tips ·
Incompatible character sets can cause an ORA-01460
I found solution but worked
FYI
create or replace function v_blobtoclob(v_blob_in in blob) return clob is
v_file_clob clob;
v_file_size integer := dbms_lob.lobmaxsize;
v_dest_offset integer := 1;
v_src_offset integer := 1;
v_blob_csid number := dbms_lob.default_csid;
v_lang_context number := dbms_lob.default_lang_ctx;
v_warning integer;
v_length number;
begin
dbms_lob.createtemporary(v_file_clob, true);
dbms_lob.converttoclob(v_file_clob,
v_blob_in,
v_file_size,
v_dest_offset,
v_src_offset,
v_blob_csid,
v_lang_context,
v_warning);
return v_file_clob;
exception
when others then
dbms_output.put_line('Error found');
end;
CREATE OR REPLACE FUNCTION clob_to_blob (p_clob CLOB, p_charsetname VARCHAR2)
RETURN BLOB
AS
l_lang_ctx INTEGER := DBMS_LOB.default_lang_ctx;
l_warning INTEGER;
l_dest_offset NUMBER := 1;
l_src_offset NUMBER := 1;
l_return BLOB;
BEGIN
DBMS_LOB.createtemporary (l_return, FALSE);
DBMS_LOB.converttoblob (
l_return,
p_clob,
DBMS_LOB.lobmaxsize,
l_dest_offset,
l_src_offset,
CASE WHEN p_charsetname IS NOT NULL THEN NLS_CHARSET_ID (p_charsetname) ELSE DBMS_LOB.default_csid END,
l_lang_ctx,
l_warning);
RETURN l_return;
END;
declare
v_hugetxt clob;
v_hugetxt2 blob;
v_hugetxt3 clob;
offset int;
clobsize number;
begin
v_hugetxt:='huge text';
select bnf.clob_to_blob(v_hugetxt,'UTF8') into v_hugetxt2 from dual;
select bnf.v_blobtoclob(v_hugetxt2) into v_hugetxt3 from dual;
dbms_output.put_line('Print CLOB');
offset:=1;
loop
exit when offset > dbms_lob.getlength(v_hugetxt3);
dbms_output.put_line(dbms_lob.substr(v_hugetxt3, 255, offset));
offset := offset + 255;
end loop;
DBMS_OUTPUT.PUT_LINE('clob out = ' || v_hugetxt3);
end;
That's it

A functions code is written in oracle. I am unable to understand what it is doing

Here is the function:
create or replace FUNCTION FUNC_PART(
p_TEXT varchar2,
p_COLUMN number,
p_SEPARATOR varchar2
) RETURN varchar2 AS
v_POS_ number;
v_POS2 number;
V_COLUMN NUMBER;
BEGIN
V_COLUMN:=p_COLUMN;
v_POS_ := 1;
v_POS2 := INSTR(p_TEXT, p_SEPARATOR, v_POS_);
WHILE (V_COLUMN >1 AND v_POS2> 0) LOOP
v_POS_ := v_POS2 + 1;
v_POS2 := INSTR(p_TEXT, p_SEPARATOR, v_POS_);
V_COLUMN :=V_COLUMN - 1;
END LOOP;
IF V_COLUMN > 1 THEN
v_POS_ := LENGTH(RTRIM(p_TEXT)) + 1;
END IF;
IF v_POS2 = 0 THEN
v_POS2 := LENGTH(RTRIM(p_TEXT)) + 1;
END IF;
RETURN SUBSTR (p_TEXT, v_POS_, v_POS2 - v_POS_);
END;
I did not understand this code carefully, but from the results, the following two pieces of code have the same meaning.
select FUNC_PART('asdfghsdfg', 3, 's')
from dual;
select regexp_substr('asdfghsdfg', '[^s]+', 1, 3)
from dual;
This is likely to be migrated from other databases to the code in oracle, because oracle does not need such a custom function

passing sys_refcursor from function to sys_refcursor out parameter in procedure

I have problem with outputting results from the sys_refcursor returned from the function stored in the variable crs_scenarios. I then want to pass this collection of data to the output parameter pout_result. I get the error PLS-00487: Invalid reference to variable 'POUT_RESULT'. Can you advise me please, how to solve this issue?
Thanks a lot!
declare
pin_scenarioName scenarios.scen_name%TYPE := 'zz_berlin_testen';
pin_scenarioRegion inf_ausbaugebiete.ausg_name%TYPE DEFAULT NULL;
pin_scenarioCutOffDate scenarios.scen_cut_off_date%TYPE DEFAULT NULL;
pin_scenarioStatus scenario_status.scst_name%TYPE DEFAULT NULL;
pout_result SYS_REFCURSOR;
pout_strerrorcode VARCHAR2(1000);
pout_strerrormessage VARCHAR2(1000);
BEGIN
pout_result := funk30.pbi$capi_export_pck.cfn_getscenarios(pin_scenarioName => pin_scenarioName,
pin_scenarioRegion => pin_scenarioRegion,
pin_scenarioCutOffDate => pin_scenarioCutOffDate,
pin_scenarioStatus => pin_scenarioStatus,
pout_strerrorcode => pout_strerrorcode,
pout_strerrormessage => pout_strerrormessage);
dbms_output.put_line(pout_result.ID || ' ' ||pout_result.NAME || ' ' ||pout_result.CUT_OFF || ' ' ||pout_result.STATUS ||
' ' || pout_result.PRIORITY || ' ' ||pout_result.PARENT_SCENARIO|| ' ' ||pout_result.REGION|| ' ' || pout_result.REMARK);
END cpr_getscenarios;
Function:
FUNCTION mfn_getscenarios(pin_scenarioName IN scenarios.scen_name%TYPE DEFAULT NULL,
pin_scenarioRegion IN inf_ausbaugebiete.ausg_name%TYPE DEFAULT NULL,
pin_scenarioCutOffDate IN scenarios.scen_cut_off_date%TYPE DEFAULT NULL,
pin_scenarioStatus IN scenario_status.scst_name%TYPE DEFAULT NULL,
pout_strerrorcode OUT VARCHAR2,
pout_strerrormessage OUT VARCHAR2)
RETURN SYS_REFCURSOR IS
crs_scenarios SYS_REFCURSOR;
n_cnt_exists NUMBER;
ex_scennotexists EXCEPTION;
strprocedurename VARCHAR2(128) := package_name || '.mfn_getScenarios';
BEGIN
BEGIN
SELECT 1 INTO n_cnt_exists FROM scenarios WHERE rownum = 1;
EXCEPTION
WHEN no_data_found THEN
RAISE ex_scennotexists;
END;
OPEN crs_scenarios FOR
SELECT scen.scen_id id,
scen.scen_name NAME,
scen.scen_cut_off_date cut_off,
scst.scst_name STATUS,
scen.scen_priority PRIORITY,
parent.scen_name PARENT_SCENARIO,
ausg.ausg_name REGION,
scen.scen_comment REMARK
FROM scenarios scen,
scenarios parent,
scenario_status scst,
inf_ausbaugebiete ausg
WHERE scen.scen_scst_id = scst.scst_id
AND scen.scen_parent_scen_id = parent.scen_id(+)
AND scen.scen_ausg_id= ausg.ausg_id(+)
AND lower(scen.scen_name) like nvl(lower('%'||pin_scenarioName||'%'), lower(scen.scen_name))
AND NVL(ausg.ausg_name, 'xxxxx') = nvl(pin_scenarioRegion, NVL(ausg.ausg_name, 'xxxxx'))
AND scen.scen_cut_off_date = nvl(pin_scenarioCutOffDate, scen.scen_cut_off_date)
AND scst.scst_name = nvl(pin_scenarioStatus, scst.scst_name);
pout_strerrorcode := '0';
pout_strerrormessage := '';
RETURN crs_scenarios;
EXCEPTION
WHEN ex_scennotexists THEN
pout_strerrorcode := 'API-00239';
pout_strerrormessage := rtrim(api_err_pck.apierrormsg('API-00239', strprocedurename), ' #');
RETURN NULL;
WHEN OTHERS THEN
pout_strerrorcode := '-1';
pout_strerrormessage := substr(SQLERRM, instr(SQLERRM, 'ORA') + 11, length(SQLERRM));
RETURN NULL;
END mfn_getscenarios;
Sys_refcursor is only a SQL definition. If you want to run it, you have to FETCH data.
So pout_result varible has no data itself.
-------- function -----
create or replace FUNCTION mfn
RETURN SYS_REFCURSOR IS
crs_scenarios SYS_REFCURSOR;
BEGIN
OPEN crs_scenarios FOR
SELECT dummy from dual;
RETURN crs_scenarios;
END ;
-- execution
set serveroutput on
declare
pout_result SYS_REFCURSOR;
type pout_result_tab is table of dual%rowtype; -- cursor datatype
pout_result_t pout_result_tab;
BEGIN
pout_result := mfn;
fetch pout_result bulk collect into pout_result_t; -- bulk collect because I assume you have recordset, not one record
dbms_output.put_line(pout_result_t(1).dummy);
END ;
/
--- Result
X
PL/SQL procedure successfully completed.

Dynamic number of bind variables in dynamic sql - Open Cursor

In My stored procedure, I have a query which is dynamic - the number of conditions in where clause varies depending on the input parameter.
in params - x, y, z
searchsql := 'select select1, select2, select3 from tableA where 1 = 1 and ';
if(x is not null) then
searchSql := searchSql || PKG_COMMON.GET_SQL_BINDTXTFLD(x,'select1','a');
-- above package will return AND upper(select1) like upper(:a)
cursorParams := cursorParams || ':' || x || ',';
end if;
if(y is not null) then
searchSql := searchSql || PKG_COMMON.GET_SQL_BINDTXTFLD(y,'select2','b');
-- above package will return AND upper(select2) like upper(:b)
cursorParams := cursorParams || ':' || y || ',';
end if;
--I am trimming the last comma of the cursor param
SELECT SUBSTR(cursorParams, 1, INSTR(cursorParams , ',', -1)-1)
INTO cursorParams FROM dual;
open resultCursor for searchSql using cursorParams
Now, i have this above cursor which needs to be opened using params passed, however in this case the number of params depends on how is sql is formed. So i am dynamically forming the bind variables using cursorParams variable
But the values are not binding, but set only to the first param
how to bind properly, i already tried execute immediate option
What's wrong with:
select select1, select2, select3
from tableA
where (:a is null
OR upper(select1) like upper(:a))
and (:b is null
OR upper(select2) like upper(:b))
and (:c is null
OR upper(select3) like upper(:c));
(Assuming that you have bind peeking disabled)
Here is a simple example of what you are asking using DBMS_SQL to dynamically build and bind.
DECLARE
i_owner all_objects.owner%TYPE := 'SYS';
i_object_name all_objects.object_name%TYPE := 'DUAL';
i_object_type all_objects.object_type%TYPE := NULL;
v_statement VARCHAR2(4000);
v_cursor BINARY_INTEGER := dbms_sql.open_cursor;
v_rows BINARY_INTEGER;
v_result SYS_REFCURSOR;
v_owner all_objects.owner%TYPE;
v_object_name all_objects.object_name%TYPE;
v_object_type all_objects.object_type%TYPE;
BEGIN
v_statement := 'select owner, object_name, object_type from all_objects where 1 = 1';
IF i_owner IS NOT NULL THEN
v_statement := v_statement ||
q'[ and owner like upper(:owner) || '%']';
END IF;
IF i_object_name IS NOT NULL THEN
v_statement := v_statement ||
q'[ and object_name like upper(:object_name) || '%']';
END IF;
IF i_object_type IS NOT NULL THEN
v_statement := v_statement ||
q'[ and object_type like upper(:object_type) || '%']';
END IF;
dbms_output.put_line(v_statement);
dbms_sql.parse(c => v_cursor,
STATEMENT => v_statement,
language_flag => dbms_sql.native);
IF i_owner IS NOT NULL THEN
dbms_sql.bind_variable(c => v_cursor,
NAME => 'owner',
VALUE => i_owner);
END IF;
IF i_object_name IS NOT NULL THEN
dbms_sql.bind_variable(c => v_cursor,
NAME => 'object_name',
VALUE => i_object_name);
END IF;
IF i_object_type IS NOT NULL THEN
dbms_sql.bind_variable(c => v_cursor,
NAME => 'object_type',
VALUE => i_object_type);
END IF;
v_rows := dbms_sql.execute(c => v_cursor);
IF v_rows >= 0 THEN
v_result := dbms_sql.to_refcursor(cursor_number => v_cursor);
LOOP
FETCH v_result
INTO v_owner,
v_object_name,
v_object_type;
EXIT WHEN v_result%NOTFOUND;
dbms_output.put_line(v_owner || ' ' || v_object_name || ' ' ||
v_object_type);
END LOOP;
END IF;
END;
/
http://docs.oracle.com/cloud/latest/db121/ARPLS/d_sql.htm
Native Dynamic SQL is an alternative to DBMS_SQL that lets you place dynamic SQL statements directly into PL/SQL blocks. In most situations, Native Dynamic SQL is easier to use and performs better than DBMS_SQL. However, Native Dynamic SQL itself has certain limitations:
There is no support for so-called Method 4 (for dynamic SQL statements with an unknown number of inputs or outputs)