I have a JSON like this:
{
"Content": [{
"Identifier": "AABBCC",
"Description": "test terfdfg",
"GenericProductIdentifier": "AABBCC",
"ProductFamilyDescription": "sampling",
"LifeCycleStatus": "ACTIVE",
"Price": {
"Value": 1.00,
"Quantity": 1000
},
"LeadTimeWeeks": "16",
"FullBoxQty": 200,
}],
"TotalElements": 1,
"TotalPages": 1,
"NumberOfElements": 1,
"First": true,
"Size": 1,
"Number": 0
}
In Delphi 10.4, I'm trying to parse it, but I can't access the values contained in 'Price'.
I wrote code like this:
var
vContent: TJSONArray;
vJson: TJSONObject;
vContentRow: TJSONObject;
i,j : Integer;
begin
Memo2.Lines.Clear;
if Memo1.Text = '' then
exit;
vJson := TJSONObject(TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(Memo1.Text),0));
try
vContent := TJSONArray(vJson.Get('Content').JsonValue);
for i := 0 to Pred(vContent.Count) do
begin
vContentRow := TJSONObject(vContent.Items[i]);
for j := 0 to Pred(vContentRow.Count) do
begin
Memo2.Lines.Add(' '+ vContentRow.Get(j).JsonString.Value+' : '+ vContentRow.Get(j).JsonValue.Value);
end;
end;
Memo2.Lines.Add(vContent.Value);
finally
end;
end;
What is the correct way to read the values contained in 'Price'?
Here is a sample code to parse your JSON:
uses
System.IOUtils, System.JSON, System.Generics.Collections;
procedure TForm1.Button1Click(Sender: TObject);
procedure GetPrices(const S: string);
var
V: TJsonValue;
O, E, P: TJsonObject;
A: TJsonArray;
begin
V := TJSONObject.ParseJSONValue(S);
if not Assigned(V) then
raise Exception.Create('Invalid JSON');
try
O := V as TJSONObject;
A := O.GetValue<TJsonArray>('Content');
for var I := 0 to A.Count - 1 do
begin
E := A.Items[I] as TJsonObject; // Element
P := E.GetValue<TJsonObject>('Price');
ShowMessage('Value: ' + P.GetValue<string>('Value') + ' ' + 'Quantity: ' + P.GetValue<string>('Quantity'));
end;
finally
V.Free;
end;
end;
var
S: string;
begin
S := TFile.ReadAllText('d:\json.txt'); // Retrieve it using some webservice
GetPrices(S);
end;
Note, your JSON is invalid, the corect definition is:
{
"Content": [{
"Identifier": "AABBCC",
"Description": "test terfdfg",
"GenericProductIdentifier": "AABBCC",
"ProductFamilyDescription": "sampling",
"LifeCycleStatus": "ACTIVE",
"Price": {
"Value": 1.00,
"Quantity": 1000
},
"LeadTimeWeeks": "16",
"FullBoxQty": 200
}],
"TotalElements": 1,
"TotalPages": 1,
"NumberOfElements": 1,
"First": true,
"Size": 1,
"Number": 0
}
You can use the JSON library of Delphi.
The JSON library has the JsonToObject class function that can convert directly the string to an Object (Object structure)
See this:
https://docwiki.embarcadero.com/Libraries/Sydney/en/REST.Json.TJson.JsonToObject
You can create the classes structure manually o using the web: https://jsontodelphi.com/
The classes structure for your JSON created is this:
type
TPrice = class;
TPrice = class
private
FQuantity: Integer;
FValue: Double;
published
property Quantity: Integer read FQuantity write FQuantity;
property Value: Double read FValue write FValue;
end;
TContent = class
private
FDescription: string;
FFullBoxQty: Integer;
FGenericProductIdentifier: string;
FIdentifier: string;
FLeadTimeWeeks: string;
FLifeCycleStatus: string;
FPrice: TPrice;
FProductFamilyDescription: string;
published
property Description: string read FDescription write FDescription;
property FullBoxQty: Integer read FFullBoxQty write FFullBoxQty;
property GenericProductIdentifier: string read FGenericProductIdentifier write FGenericProductIdentifier;
property Identifier: string read FIdentifier write FIdentifier;
property LeadTimeWeeks: string read FLeadTimeWeeks write FLeadTimeWeeks;
property LifeCycleStatus: string read FLifeCycleStatus write FLifeCycleStatus;
property Price: TPrice read FPrice;
property ProductFamilyDescription: string read FProductFamilyDescription write FProductFamilyDescription;
public
constructor Create;
destructor Destroy; override;
end;
TRoot = class(TJsonDTO)
private
[JSONName('Content'), JSONMarshalled(False)]
FContentArray: TArray<TContent>;
[GenericListReflect]
FContent: TObjectList<TContent>;
FFirst: Boolean;
FNumber: Integer;
FNumberOfElements: Integer;
FSize: Integer;
FTotalElements: Integer;
FTotalPages: Integer;
function GetContent: TObjectList<TContent>;
protected
function GetAsJson: string; override;
published
property Content: TObjectList<TContent> read GetContent;
property First: Boolean read FFirst write FFirst;
property Number: Integer read FNumber write FNumber;
property NumberOfElements: Integer read FNumberOfElements write FNumberOfElements;
property Size: Integer read FSize write FSize;
property TotalElements: Integer read FTotalElements write FTotalElements;
property TotalPages: Integer read FTotalPages write FTotalPages;
public
destructor Destroy; override;
end;
Now, the code for parse elements is more simple. You only need a code like this to access different properties of your structure:
var
Root: TRoot;
begin
root := TJSON.JsonToObject<TRoot>(Memo1.Lines.Text);
lblid.Caption := 'TotalElements: ' + Root.TotalElements.ToString;
lblvalue.Caption := 'TotalPages: ' + Root.TotalPages.ToString;
lblcount.Caption := 'Identifier: ' + Root.Content[0].Identifier;
lblfirstonclick.Caption := 'Description: ' + Root.Content[0].Description;
lbllastonclick.Caption := 'Price/Quantity:' + Root.Content[0].Price.Quantity.ToString;
//...
Try this, i make some helper for TFDMemtable. Simple to uses, no need parsing everytime you have other JSON.
const
JSONString =
'{
"Content": [{
"Identifier": "AABBCC",
"Description": "test terfdfg",
"GenericProductIdentifier": "AABBCC",
"ProductFamilyDescription": "sampling",
"LifeCycleStatus": "ACTIVE",
"Price": {
"Value": 1.00,
"Quantity": 1000
},
"LeadTimeWeeks": "16",
"FullBoxQty": 200,
}],
"TotalElements": 1,
"TotalPages": 1,
"NumberOfElements": 1,
"First": true,
"Size": 1,
"Number": 0
}';
begin
if not Memtable.FillDataFromString(JSONString) then begin
ShowMessages(Memtable.FieldByName('messages').AsString);
end else begin
Memtable.FillDataFromString(Memtable.FieldByName('Content').AsString);
ShowMessages(Memtable.FieldByName('Price').AsString);
end;
end;
====
unit BFA.Helper.MemTable;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types,
System.Rtti, FMX.Grid.Style, FMX.Grid, FMX.ScrollBox, FMX.Memo, FMX.Edit,
FMX.Controls.Presentation, FMX.StdCtrls, FireDAC.Stan.Intf,
FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS,
FireDAC.Phys.Intf, FireDAC.DApt.Intf, System.Net.URLClient,
System.Net.HttpClient, System.Net.HttpClientComponent, Data.DB,
FireDAC.Comp.DataSet, FireDAC.Comp.Client, System.JSON, System.Net.Mime;
type
TFDMemTableHelper = class helper for TFDMemTable
procedure FillError(FMessage, FError : String);
function FillDataFromString(FJSON : String) : Boolean; //ctrl + shift + C
end;
implementation
{ TFDMemTableHelper }
function TFDMemTableHelper.FillDataFromString(FJSON: String): Boolean; //bug memoryleak fix at checking is Object / array soon
const
FArr = 0;
FObj = 1;
FEls = 2;
function isCheck(FString : String) : Integer; begin
Result := FEls;
var FCheck := TJSONObject.ParseJSONValue(FJSON);
if FCheck is TJSONObject then
Result := FObj
else if FCheck is TJSONArray then
Result := FArr;
FCheck.DisposeOf;
end;
var
JObjectData : TJSONObject;
JArrayJSON : TJSONArray;
JSONCheck : TJSONValue;
begin
var FResult := isCheck(FJSON);
try
Self.Active := False;
Self.Close;
Self.FieldDefs.Clear;
if FResult = FObj then begin
JObjectData := TJSONObject.ParseJSONValue(FJSON) as TJSONObject;
end else if FResult = FArr then begin
JArrayJSON := TJSONObject.ParseJSONValue(FJSON) as TJSONArray;
JObjectData := TJSONObject(JArrayJSON.Get(0));
end else begin
Self.FillError('FAILED PARSING JSON', 'THIS IS NOT JSON');
Result := False;
Exit;
end;
for var i := 0 to JObjectData.Size - 1 do begin
Self.FieldDefs.Add(
StringReplace(JObjectData.Get(i).JsonString.ToString, '"', '', [rfReplaceAll, rfIgnoreCase]),
ftString,
100000,
False
);
end;
Self.CreateDataSet;
Self.Active := True;
Self.Open;
try
if FResult = FArr then begin
for var i := 0 to JArrayJSON.Size - 1 do begin
JObjectData := TJSONObject(JArrayJSON.Get(i));
Self.Append;
for var ii := 0 to JObjectData.Size - 1 do begin
JSONCheck := TJSONObject.ParseJSONValue(JObjectData.GetValue(Self.FieldDefs[ii].Name).ToJSON);
if JSONCheck is TJSONObject then
Self.Fields[ii].AsString := JObjectData.GetValue(Self.FieldDefs[ii].Name).ToJSON
else if JSONCheck is TJSONArray then
Self.Fields[ii].AsString := JObjectData.GetValue(Self.FieldDefs[ii].Name).ToJSON
else
Self.Fields[ii].AsString := JObjectData.Values[Self.FieldDefs[ii].Name].Value;
JSONCheck.DisposeOf;
end;
Self.Post;
end;
end else begin
Self.Append;
for var ii := 0 to JObjectData.Size - 1 do begin
JSONCheck := TJSONObject.ParseJSONValue(JObjectData.GetValue(Self.FieldDefs[ii].Name).ToJSON);
if JSONCheck is TJSONObject then
Self.Fields[ii].AsString := JObjectData.GetValue(Self.FieldDefs[ii].Name).ToJSON
else if JSONCheck is TJSONArray then
Self.Fields[ii].AsString := JObjectData.GetValue(Self.FieldDefs[ii].Name).ToJSON
else
Self.Fields[ii].AsString := JObjectData.Values[Self.FieldDefs[ii].Name].Value;
JSONCheck.DisposeOf;
end;
Self.Post;
end;
Result := True;
except
on E : Exception do begin
Result := False;
Self.FillError('Error Parsing JSON', E.Message);
end;
end;
finally
if FResult = FObj then
JObjectData.DisposeOf;
if FResult = FArr then
JArrayJSON.DisposeOf;
Self.First;
end;
end;
procedure TFDMemTableHelper.FillError(FMessage, FError : String);
begin
Self.Active := False;
Self.Close;
Self.FieldDefs.Clear;
Self.FieldDefs.Add('status', ftString, 200, False);
Self.FieldDefs.Add('messages', ftString, 200, False);
Self.FieldDefs.Add('data', ftString, 200, False);
Self.CreateDataSet;
Self.Active := True;
Self.Open;
Self.Append;
Self.Fields[0].AsString := FError;
Self.Fields[1].AsString := FMessage;
Self.Post;
end;
end.
Related
I have to write a function for this app that has a grid where you can input value in rows, delete rows, update them etc.
When you do such operation, a JSON file is sent as payload and I can handle it with a procedure, but I am having problems with situations where a user wants to edit multiple rows, it updates just one of them.
This is an example of JSON file, where the description key is the one that is changed from null to the one with values (v1, v2, v3)
If value in grid is changed, it includes key "Changed" with value 1, in case of delete "Deleted" and so on.
{
"Changes": [
{
"id": "AR46",
"Changed": 1,
"SESSION_ID": "963",
"NAME": "IMAGE_LOGO",
"VALUE": "",
"DESCRIPTION": "v1",
"TYPE": "IMAGE",
"PARAM_GROUP": "J",
"BLOB_VALUE": "oracle.sql.BLOB#2ba32",
"EDIT": "Edit",
"DOWNLOAD": "Download",
"CLOB_VALUE": "oracle.sql.CLOB#7fd86843",
"XML_VALUE": "",
"CREATE_DATE": "11.03.2022 13:04:26",
"_DefaultSort": ""
},
{
"id": "AR47",
"Changed": 1,
"SESSION_ID": "963",
"NAME": "IMAGE_HPB_MEMO_FOOTER",
"VALUE": "",
"DESCRIPTION": "v2",
"TYPE": "IMAGE",
"PARAM_GROUP": "JASPER",
"BLOB_VALUE": "oracle.sql.BLOB#7621f9df",
"EDIT": "Edit",
"DOWNLOAD": "Download",
"CLOB_VALUE": "oracle.sql.CLOB#43e24152",
"XML_VALUE": "",
"CREATE_DATE": "11.03.2022 13:04:35",
"_DefaultSort": ""
},
{
"id": "AR48",
"Changed": 1,
"SESSION_ID": "963",
"NAME": "IMAGE_HPB_MEMO_INVCRED",
"VALUE": "",
"DESCRIPTION": "v3",
"TYPE": "IMAGE",
"PARAM_GROUP": "JASPER",
"BLOB_VALUE": "oracle.sql.BLOB#762074f6",
"EDIT": "Edit",
"DOWNLOAD": "Download",
"CLOB_VALUE": "oracle.sql.CLOB#4a068001",
"XML_VALUE": "",
"CREATE_DATE": "11.03.2022 13:04:46",
"_DefaultSort": ""
}
]
}
And this is the function that I wrote that works for just one edit/update. There aren't any errors if you try to update multiple rows, but still, just one(the first) one, is changed.
create or replace function changesResources (p_data varchar2)
return varchar2
IS
l_nullEx exception;
PRAGMA EXCEPTION_INIT(l_nullEx, -1400);
p_rez varchar2(100);
p_session_id number;
p_name varchar2(100);
p_value VARCHAR2(500);
p_description VARCHAR2(1000);
p_type VARCHAR2(100);
p_param_group VARCHAR2(100);
p_blob_value VARCHAR2(1000);
p_clob_value VARCHAR2(1000);
p_xml_value VARCHAR2(1000);
p_create_date varchar2(50);
l_json_obj JSON_OBJECT_T;
l_json_arr JSON_ARRAY_T;
Begin
l_json_obj := JSON_OBJECT_T.PARSE(p_data);
l_json_arr := l_json_obj.get_array('Changes');
FOR i IN 0..l_json_arr.get_size()-1 LOOP
p_session_id := JSON_VALUE(l_json_arr.get(i).to_string(), '$.SESSION_ID');
p_name := JSON_VALUE(l_json_arr.get(i).to_string(), '$.NAME');
p_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.VALUE');
p_description := JSON_VALUE(l_json_arr.get(i).to_string(), '$.DESCRIPTION');
p_type := JSON_VALUE(l_json_arr.get(i).to_string(), '$.TYPE');
p_param_group := JSON_VALUE(l_json_arr.get(i).to_string(), '$.PARAM_GROUP');
p_blob_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.BLOB_VALUE');
p_clob_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.CLOB_VALUE');
p_xml_value := JSON_VALUE(l_json_arr.get(i).to_string(), '$.XML_VALUE');
p_create_date := JSON_VALUE(l_json_arr.get(i).to_string(), '$.CREATE_DATE');
IF JSON_VALUE(l_json_arr.get(i).to_string(), '$.Changed') = 1
THEN
UPDATE BF_RESOURCES_CONF
SET description = p_description,
value=p_value,
type = p_type,
param_group = p_param_group,
blob_value = utl_raw.cast_to_raw(p_blob_value),
clob_value = TO_CLOB(p_clob_value),
xml_value=p_xml_value,
create_date = TO_DATE(p_create_date,'DD.MM.YYYY HH24:MI:SS')
where session_id = p_session_id
and name = p_name;
p_rez := '1|success!';
return p_rez;
ELSIF JSON_VALUE(l_json_arr.get(i).to_string(), '$.Deleted') = 1
THEN
DELETE FROM BF_RESOURCES_CONF
WHERE session_id = p_session_id
and name = p_name;
p_rez := '1|success!';
return p_rez;
ELSE
INSERT INTO BF_RESOURCES_CONF (session_id,name, value,description, type,param_group,blob_value,clob_value,xml_value,create_date) VALUES (p_session_id, p_name, p_value, p_description, p_type, p_param_group, utl_raw.cast_to_raw(p_blob_value),TO_CLOB(p_clob_value),p_xml_value,TO_DATE(p_create_date,'DD.MM.YYYY HH24:MI:SS'));
p_rez := '1|success!';
return p_rez;
END IF;
END LOOP;
EXCEPTION
WHEN l_nullEx THEN
p_rez := '-1|Columns SESSION_ID, NAME I CREATE_DATE have to contain values!';
RETURN p_rez;
--WHEN OTHERS THEN
-- p_rez := '-1|Error!';
-- RETURN p_rez;
END changesResources ;
On 12.1 and above JSON_TABLE is available. Here is an example on the emp sample table similar to yours:
CREATE OR REPLACE function update_emp (p_data VARCHAR2)
RETURN VARCHAR2
IS
l_result VARCHAR2(1000);
BEGIN
FOR r IN (
with json_doc AS
(SELECT p_data AS json_data FROM dual
)
SELECT
empno,
changed,
deleted,
salary
FROM
json_doc t,
JSON_TABLE(json_data, '$.Changes[*]'
COLUMNS (
empno NUMBER PATH '$.empno',
changed NUMBER PATH '$.Changed',
deleted NUMBER PATH '$.Deleted',
salary NUMBER PATH '$.Salary'
))
) LOOP
IF r.changed = 1 THEN
UPDATE emp SET sal = r.salary WHERE empno = r.empno;
l_result := l_result || ', updated: '||r.empno;
ELSIF r.deleted = 1 THEN
DELETE FROM emp WHERE empno = r.empno;
l_result := l_result || ', deleted: '||r.empno;
END IF;
END LOOP;
RETURN LTRIM(l_result, ', ');
END update_emp;
/
set serveroutput on size 999999
clear screen
declare
l_data varchar2(1000);
l_return varchar2(1000);
begin
l_data := '{
"Changes": [
{
"empno": 7698,
"Changed": 1,
"Salary": 4000,
},
{
"empno": 7788,
"Changed": 1,
"Salary": 5000,
},
{
"empno": 7876,
"Deleted": 1
}
]
}';
l_return := update_emp(p_data => l_data);
dbms_output.put_line('l_return = ' || l_return);
end;
/
l_return = updated: 7698, updated: 7788, deleted: 7876
PL/SQL procedure successfully completed.
Need to create a function which takes input of CLOB and I need to remove array matching the condition.
create or replace FUNCTION remove_config_node_by_key (
p_in_json IN CLOB,
p_in_key IN VARCHAR2
) RETURN CLOB IS
l_ja json_array_t;
l_po json_object_t;
l_key VARCHAR2(500);
BEGIN
l_ja := json_array_t.parse(p_in_json);
FOR idx IN 0.. l_ja.get_size - 1 LOOP
l_po := json_object_t(l_ja.get(idx));
l_key := l_po.get_string('key');
-- check if the key matches with input and then delete that node.
dbms_output.put('Key to remove in the JSON: ' || l_key);
IF l_key = p_in_key THEN
dbms_output.put('Key to remove in the JSON: ' || l_key);
l_ja.remove (idx);
-- dbms_output.new_line;
dbms_output.put('Key is removed in the JSON: ' || l_key);
END IF;
END LOOP;
RETURN l_ja.to_clob;
END;
When called with:
update COLD_DRINKS cd set cd.configuration = remove_config_node_by_key(cd.configuration, 'b')
where country='INDIA';
I get error:
Error report -
ORA-30625: method dispatch on NULL SELF argument is disallowed
ORA-06512: at "SYS.JSON_OBJECT_T", line 72
ORA-06512: at "PLATFORM_ADMIN_DATA.REMOVE_CONFIG_NODE_BY_KEY", line 11
input JSON:
[
{
"key": "a",
"value": "lemon soda"
},
{
"key": "b",
"value": "Coke"
},
{
"key": "c",
"value": "Pepsi"
}
]
Expected JSON after execution:
[
{
"key": "a",
"value": "lemon soda"
},
{
"key": "c",
"value": "Pepsi"
}
]
I think something is wrong about this l_ja.remove (idx); as this one causes the exception. Not able to remove the object at index.
In 18c at least it works with your sample data (with the trailing comma removed from the array), but it gets that error with a null configuration.
So you can either test for null in your function, or exclude nulls from your update, or fix your data so it doesn't have nulls.
The simplest thing to do is probably add a null check:
...
BEGIN
IF p_in_json IS NULL THEN
RETURN NULL;
END IF;
l_ja := json_array_t.parse(p_in_json);
...
fiddle
You can also remove it using json_transform:
create or replace function remove_config_node_by_key (
p_in_json IN CLOB,
p_in_key IN VARCHAR2
) RETURN CLOB IS
l_result CLOB ;
begin
execute immediate q'~select json_transform(
:p_in_json,
REMOVE '$[*]?(#.key == "~' || p_in_key || q'~")'
)
from dual~' into l_result using p_in_json
;
return l_result ;
end ;
/
(with all usual comments regarding possible SQL injection...)
The issue was resolved when I added REVERSE in for loop
before [ERROR]
FOR idx IN 0.. l_ja.get_size - 1
after [PASS]
FOR idx IN REVERSE 0.. l_ja.get_size - 1
Complete working function
CREATE OR REPLACE FUNCTION remove_config_node_by_key (
p_in_json IN CLOB,
p_in_key IN VARCHAR2
) RETURN CLOB IS
l_ja json_array_t := json_array_t ();
l_po json_object_t;
l_key VARCHAR2(500);
BEGIN
l_ja := json_array_t.parse(p_in_json);
FOR idx IN REVERSE 0.. l_ja.get_size - 1
LOOP
l_po := json_object_t(l_ja.get(idx));
l_key := l_po.get_string('key');
-- check if the key matches with input and then delete that node.
IF l_key = p_in_key THEN
dbms_output.put_line('Key to remove in the JSON: ' || l_key || ' at index : ' || idx);
l_ja.remove (idx);
dbms_output.put_line('Key is removed in the JSON: ' || l_key);
END IF;
END LOOP;
RETURN l_ja.to_clob;
END;
/
I need to MERGE two JSONB_ARRAYS
i have in my table column jsonb of items which looks like this:
[
{"fav": 1, "is_active": true, "date": "1999-00-00 11:07:05.710000"},
{"fav": 2, "is_active": true, "date": "1998-00-00 11:07:05.710000"}
]
where fav's value is unique number.
And i have incoming data, where could be the same items which also exists in my table and also new items and after merging the result must be that way where new items just need to add but existing items i need to update
so after merging the result must look like this:
merge:
[
{"fav": 1, "is_active": true, "date": "1999-00-00 11:07:05.710000"},
{"fav": 2, "is_active": true, "date": "1998-00-00 11:07:05.710000"}
]::jsonb ||
[
{"fav": 3, "is_active": true, "date": "2019-00-00 11:07:05.710000"},
{"fav": 1, "is_active": false, "date": "2020-00-00 11:07:05.710000"}
]::jsonb
------------------------------------------------------------------------
result:
[
{"fav": 1, "is_active": false, "date": "2020-00-00 11:07:05.710000"},
{"fav": 2, "is_active": true, "date": "1998-00-00 11:07:05.710000"},
{"fav": 3, "is_active": true, "date": "2019-00-00 11:07:05.710000"}
]
as expected the "fav": 1 -> was updated and "fav": 3 -> was added
maybe i need some refactroring structure of my json maybe something else?
and maybe it would be better if i retrieve json to Collection and work with objects and after all manipulations just save it back?
Update 1
i try write custom function:
CREATE OR REPLACE FUNCTION public.json_array_merge(data1 jsonb, merge_data jsonb)
RETURNS jsonb
IMMUTABLE
LANGUAGE sql
AS $$
SELECT jsonb_agg(expression)::jsonb
FROM (
WITH to_merge AS (
SELECT * FROM jsonb_each(jsonb_array_elements(merge_data))
)
SELECT *
FROM json_each(jsonb_array_elements(data1))
WHERE value NOT IN (SELECT value FROM to_merge)
UNION ALL
SELECT * FROM to_merge
) expression;
$$;
but now it doesnt work (
You probably will want to write a custom function to handle this. The default behaviour is to append each value because it has no way of knowing that you want fav to be unique.
If your data used fav as a key e.g.
{
"fav1": {"date": "2020-00-00 11:07:05.710000", "is_active": false},
"fav2": {"date": "1998-00-00 11:07:05.710000", "is_active": true},
"fav3": {"date": "2019-00-00 11:07:05.710000", "is_active": true}
}
this would be simple to manage, but since you are using an array you would need to make a custom function that iterates and check each value.
Edit you would need to run a few loops with plpgsql, this could be achieved more efficiently using plv8
CREATE OR REPLACE FUNCTION public.json_array_merge(
data_new jsonb,
data_old jsonb,
key_val text
)
RETURNS jsonb
AS $$
DECLARE
ret jsonb := '[]'::jsonb;
cur text;
add boolean := true;
i integer := 0;
ic integer := jsonb_array_length(data_old);
j integer := 0;
jc integer := jsonb_array_length(data_new);
BEGIN
IF ic > 0 AND jc > 0 THEN
-- populate or replace the records that are already there
WHILE i < ic LOOP
cur := null;
j := 0;
-- loop new array
WHILE j < jc LOOP
IF data_old->i->>key_val = data_new->j->>key_val THEN
cur := data_new->>j;
add := false;
END IF;
j := j + 1;
END LOOP;
-- add or replace
IF add THEN
ret := ret || format('[%s]', data_old->>i)::jsonb;
ELSE
ret := ret || format('[%s]', cur)::jsonb;
END IF;
add := true;
i := i + 1;
END LOOP;
-- loop through the new data again and add any values not in ret
ic := jsonb_array_length(ret);
j := 0;
WHILE j < jc LOOP
i := 0;
add := true;
WHILE i < ic LOOP
IF ret->i->>key_val = data_new->j->>key_val THEN
add := false;
END IF;
i := i + 1;
END LOOP;
IF add THEN
ret := ret || format('[%s]', data_new->>j)::jsonb;
END IF;
j := j + 1;
END LOOP;
ELSE
ret := data_new;
END IF;
RETURN ret;
END
$$
LANGUAGE plpgsql IMMUTABLE;
Running this should give you the desired result
SELECT json_array_merge(
'[{"fav": 3, "is_active": true, "date": "2019-00-00 11:07:05.710000"},{"fav": 1, "is_active": false, "date": "2020-00-00 11:07:05.710000"}]',
'[{"fav": 1, "is_active": true, "date": "1999-00-00 11:07:05.710000"},{"fav": 2, "is_active": true, "date": "1998-00-00 11:07:05.710000"}]',
'fav'
)
I am trying to extract the value from "response.AAPL.results.year_high.data" since there is two value. I have found many examples, but the value bracket in data is all individually identify by a title, mine is not. Does anybody know's how to access the information ?
in Oracle 12
thanks in advance.
create or replace procedure test(p_where in varchar2, p_radius in number, p_room in number)
is
begin
DECLARE
l_param_list VARCHAR2(512);
l_http_request UTL_HTTP.req;
l_http_response UTL_HTTP.resp;
l_response_text VARCHAR2(32767);
l_members WWV_FLOW_T_VARCHAR2;
l_count PLS_INTEGER;
l_list json_list;
obj json := json();
l_json_values apex_json.t_values;
arr json_list := json_list();
l_paths apex_t_varchar2;
BEGIN
l_response_text := '{"response": {"AAPL": {"meta": {"status": "ok"}, "results": {"year_high": {"meta": {"status": "ok"}, "data": [["2016-09-30", 123.8200], ["2016-09-29", 125.0000]]}}}, "MSFT": {"meta": {"status": "ok"}, "results": {"year_high": {"meta": {"status": "ok"}, "data": ["2016-09-30", 58.7000]}}}}}';
/* DBMS_OUTPUT.put_line(l_response_text);*/
apex_json.parse (l_response_text);
/* dbms_output.put_line (apex_json.get_varchar2(p_path => 'count')); */
/* dbms_output.put_line (apex_json.get_number (p_path => 'response.MSFT.results.price.data',p0=>2,p_values =>l_json_values));*/
DBMS_OUTPUT.put_line('----------------------------------------');
DBMS_OUTPUT.put_line('Check elements (members) below a path');
l_members := APEX_JSON.get_members(p_path=>'response.AAPL.results.year_high');
DBMS_OUTPUT.put_line('Members Count : ' || l_members.COUNT);
FOR i IN 1 .. l_members.COUNT LOOP
DBMS_OUTPUT.put_line('Member Item Idx : ' || i);
DBMS_OUTPUT.put_line('Member Name : ' || l_members(i));
END LOOP;
/* This is were I would like to extract the value in the data member*/
DBMS_OUTPUT.put_line('----------------------------------------');
DBMS_OUTPUT.put_line('Employee Information (Loop through array)');
l_count := APEX_JSON.get_count(p_path => 'response.AAPL.results.year_high.data');
DBMS_OUTPUT.put_line('Employees Count : ' || l_count);
FOR i IN 1 .. l_count LOOP
DBMS_OUTPUT.put_line('Employee Item Idx : ' || i);
DBMS_OUTPUT.put_line('Employee Number : ' ||
APEX_JSON.get_varchar2(p_path => 'response.AAPL.results.year_high.data[%d]', p0 => i));
DBMS_OUTPUT.put_line('Employee Name : ' ||
APEX_JSON.get_varchar2(p_path => 'response.AAPL.results.year_high.data[%d]', p0 => i));
END LOOP;
/* dbms_output.put_line (apex_json.get_varchar2 ('response.MSFT.results.year_high.data[%d]', 1));
dbms_output.put_line (apex_json.get_varchar2('response.MSFT.results.year_high.data[%d]', 2));
dbms_output.put_line (apex_json.get_varchar2 ('response.AAPL.results.year_high.data[%d]',1));
dbms_output.put_line (apex_json.get_varchar2('response.AAPL.results.year_high.data[%d]',2));
*/
end;
end test;
First of, it's very weird behavior to have an array of different data types. i.e.: [["2016-09-30", 123.8200], ["2016-09-29", 125.0000]]
Normally you would have an array of dates, array of numbers, array of text. Not mixed.
As you can see the data is an array of arrays. So you need to address that:
declare
json varchar2 (32767)
:= '{"response": {"AAPL": {"meta": {"status": "ok"}, "results": {"year_high": {"meta": {"status": "ok"}, "data": [["2016-09-30", 123.8200], ["2016-09-29", 125.0000]]}}}, "MSFT": {"meta": {"status": "ok"}, "results": {"year_high": {"meta": {"status": "ok"}, "data": ["2016-09-30", 58.7000]}}}}}';
begin
apex_json.parse (json);
dbms_output.put_line ('First value: ' || apex_json.get_varchar2 ('response.AAPL.results.year_high.data[1][1]'));
dbms_output.put_line ('Second value: ' || apex_json.get_number ('response.AAPL.results.year_high.data[1][2]'));
end;
Will output:
First value: 2016-09-30
Second value: 123,82
EDIT:
To use a loop, this why it's bad to mix types in an array. Thankfully get_varchar2 will cast a number to text and not result in an error:
declare
json varchar2 (32767)
:= '{"response": {"AAPL": {"meta": {"status": "ok"}, "results": {"year_high": {"meta": {"status": "ok"}, "data": [["2016-09-30", 123.8200], ["2016-09-29", 125.0000]]}}}, "MSFT": {"meta": {"status": "ok"}, "results": {"year_high": {"meta": {"status": "ok"}, "data": ["2016-09-30", 58.7000]}}}}}';
begin
apex_json.parse (json);
for x in 1 .. nvl (apex_json.get_count ('response.AAPL.results.year_high.data'), -1) loop
for y in 1 .. nvl (apex_json.get_count ('response.AAPL.results.year_high.data[%d]', x), -1) loop
dbms_output.put_line ('First value: ' || apex_json.get_varchar2 ('response.AAPL.results.year_high.data[%d][%d]', x, y));
end loop;
end loop;
end;
Outputs:
First value: 2016-09-30
First value: 123.82
First value: 2016-09-29
First value: 125
I got the following code:
procedure TForm2.Button1Click(Sender: TObject);
Const
StrJson=
'{'+
' "products": {'+
' "Men''s Sneakers": {'+
' "instock": false,'+
' "size": "423",'+
' "manufacturer": "阿迪达斯",'+
' "lastcheck": "20120529"'+
' },'+
' "Purse": {'+
' "instock": true,'+
' "size": "not applicable",'+
' "manufacturer": "普拉达",'+
' "lastcheck": "20120528"'+
' },'+
' "Men''s Hood": {'+
' "instock": false,'+
' "size": "M",'+
' "manufacturer": "通用",'+
' "lastcheck": "20120529"'+
' }'+
' },'+
' "total": 41,'+
' "available": 30'+
'}';
var
LJsonObj : TJSONObject;
LJPair : TJSONPair;
LProducts : TJSONValue;
LProduct : TJSONValue;
LItem : TJSONValue;
LIndex : Integer;
LSize : Integer;
begin
LJsonObj := TJSONObject.ParseJSONValue(TEncoding.ASCII.GetBytes(StrJson),0) as TJSONObject;
try
LProducts := LJsonObj.Get('products').JsonValue;
LSize := TJSONArray(LProducts).Size;
for LIndex:=0 to LSize-1 do
begin
LProduct := TJSONArray(LProducts).Get(LIndex);
LJPair := TJSONPair(LProduct);
memo1.lines.add(Format('Product Name %s',[LJPair.JsonString.Value]));
for LItem in TJSONArray(LJPair.JsonValue) do
begin
if TJSONPair(LItem).JsonValue is TJSONFalse then
memo1.lines.add(Format(' %s : %s',[TJSONPair(LItem).JsonString.Value, 'false']))
else
if TJSONPair(LItem).JsonValue is TJSONTrue then
memo1.lines.add(Format(' %s : %s',[TJSONPair(LItem).JsonString.Value, 'true']))
else
memo1.lines.add(Format(' %s : %s',[TJSONPair(LItem).JsonString.Value, TJSONPair(LItem).JsonValue.Value]));
end;
end;
finally
LJsonObj.Free;
end;
end;
It proceduces the following result:
Product Name Men's Sneakers
instock : false
size : 423
manufacturer : ????
lastcheck : 20120529
Product Name Purse
instock : true
size : not applicable
manufacturer : ???
lastcheck : 20120528
Product Name Men's Hood
instock : false
size : M
manufacturer : ??
lastcheck : 20120529
My question is how do i get the chinese characters parsed out instead of the ??? symbols.
Is there some setting i am missing?
It is obvious because you are using TEncoding.ASCII.GetBytes. You should use TEncoding.UTF8.GetBytes.
You can also use the TJSONObject.ParseJSONValue overload that takes a string directly.