1

I am trying to create a global procedure which can append a JSON field to a main JSON object dynamically.

procedure TJSONAdapter.addField(Key, Field: String);
var
  i: Integer;
  strArr: TStringList;
  j: Integer;

  pJSONObj: ^TJSONObject;
begin
  strArr:= TStringList.Create;
  Split('.', Key, strArr);

  pJSONObj:= @STATICJSON;

  i:= 0;
  repeat
  begin
    if IsSimpleJsonValue(STATICJSON.GetValue(strArr[i])) then
    begin
      Exit;
    end;

    if STATICJSON.GetValue(strArr[i]) is TJSONObject then
    begin
      pJSONObj:= @(STATICJSON.GetValue(strArr[i]) as TJSONObject); -> error in this line
      if i + 1 = strArr.Count then
      begin

I store my values in a JSON object named STATICJSON until the program closes, and then I save it to file.

I am trying to add a field somewhere in STATICJSON. For example;

{
  "colors" :
    {
      "id": "F00",
      "color": "red",
      "details" : [
        {
         "name_en": "Red",
        },
        {
         "name_de": "Rot"
        }
      ]
   },
   {
      "id": "0F0",
      "color": "green",
      "details" : [
        {
         "name_en": "Green",
        },
        {
         "name_de": "Grün"
        }
      ]
   }
 }

If I want to add a code field in every details of the first object in the colors array:

obj.addField('colors.0.details.X', 'code');

What I expected :

{
  "colors" :
    {
      "id": "F00",
      "color": "red",
      "details" : [
        {
         "name_en": "Red",
         "code": ""
        },
        {
         "name_de": "Rot",
         "code": ""
        }
      ]
   },
   {
      "id": "0F0",
      "color": "green",
      "details" : [
        {
         "name_en": "Green",
        },
        {
         "name_de": "Grün"
        }
      ]
   }
 }

So I am trying to go to the details array of the first color and addField using a pointer.

I was using:

STATICJSON:= STATICJSON.GetValue(strArr[i]) as TJSONObject;

and it works well, but STATICJSON content is changed to this:

"details" : [
            {
             "name_en": "Red",
             "code": ""
            },
            {
             "name_de": "Rot",
             "code": ""
            }
          ]

So I lost all other JSON content. This is why I want to use a pointer. But I'm getting an error on this line:

pJSONObj:= @(STATICJSON.GetValue(strArr[i]) as TJSONObject);

Variable required

UPDATE :

procedure TJSONAdapter.addField(Key, Field: String);
var
  i: Integer;
  strArr: TStringList;
  j: Integer;

begin

  strArr:= TStringList.Create;
  Split('.', Key, strArr);

  i:= 0;
  repeat
  begin
    if IsSimpleJsonValue(STATICJSON.GetValue(strArr[i])) then
    begin
      Exit;
    end;

    if STATICJSON.GetValue(strArr[i]) is TJSONObject then
    begin
      STATICJSON:= STATICJSON.GetValue(strArr[i]) as TJSONObject;
      if i + 1 = strArr.Count then
      begin
        STATICJSON.AddPair(TJSONPair.Create(Field, ''));
        Exit;
      end;
    end;

    if STATICJSON.GetValue(strArr[i]) is TJSONArray then
    begin
      if strArr[i + 1] = 'X' then
      begin
        for j := 0 to (STATICJSON.GetValue(strArr[i]) as TJSONArray).Size -1 do
        begin
          ((STATICJSON.GetValue(strArr[i]) as TJSONArray).Get(j) as TJSONObject).AddPair(TJSONPair.Create(Field, ''));
        end;
        Exit;
      end;

      STATICJSON:= (STATICJSON.GetValue(strArr[i]) as TJSONArray).Get(StrToInt(strArr[i+1])) as TJSONObject;
      i:= i+1;

    end;
    Inc(i);
  end;
  until i = strArr.Count;

  strArr.Free;
end;

It works, but because of these lines:

STATICJSON:= STATICJSON.GetValue(strArr[i]) as TJSONObject;

STATICJSON:= (STATICJSON.GetValue(strArr[i]) as TJSONArray).Get(StrToInt(strArr[i+1])) as TJSONObject;

I lost the main JSON content, so I don't want to assign STATICJSON, i just want to assign its address to a pointer to not lose its content.

3
  • 1
    Why do you that pointer stuff? TJSONObject is already a pointer, to an object. So, just for being sure, do you need to have a pointer to a pointer? Commented Feb 20, 2018 at 6:28
  • @Victoria ok but how can i change the content of json in tjsonobject ? i dont know which field will change, before user send me input. Also i dont know the json content. Commented Feb 20, 2018 at 6:35
  • 1
    @Nevermore Just change it by calling methods that mutate its state. E.g. AddPair or SetPairs or RemovePair Commented Feb 20, 2018 at 6:44

1 Answer 1

1

Delphi objects are reference types. TJSONObject is already a pointer, so there is no need to use ^TJSONObject.

Try something more like this:

var
  STATICJSON: TJSONObject = nil;

procedure TJSONAdapter.addField(Key, Field: String);
var
  I, J, Index: Integer;
  strArr: TStringList;
  pJSONVal: TJSONValue;
begin
  strArr := TStringList.Create;
  try
    Split('.', Key, strArr);
    if STATICJSON = nil then begin
      STATICJSON := TJSONObject.Create;
    end;
    pJSONVal := STATICJSON;
    For I := 0 to strArr.Count-1 then
    begin
      if TryStrToInt(strArr[I], Index) then
      begin
        if not (pJSONVal is TJSONArray) then Exit; 
        pJSONVal := TJSONArray(pJSONVal).Get(Index);
      end
      else if strArr[I] = '*' then
      begin
        if I <> (strArr.Count-1) then Exit;
        if not (pJSONVal is TJSONArray) then Exit; 
        with TJSONArray(pJSONVal) do
        begin
          For J := 0 to Count-1 do
          begin
            pJSONVal := Get(Index);
            if pJSONVal is TJSONObject then
              TJSONObject(pJSONVal).AddPair(Field, '');
          end;
        end;
      end
      else if pJSONVal is TJSONObject then
      begin
        pJSONVal := TJSONObject(pJSONVal).Get(strArr[I]);
        if pJSONVal = nil then Exit;
      end
      else Exit;
    end;
    if pJSONVal is TJSONObject then
      TJSONObject(pJSONVal).AddPair(Field, '');
  finally
    strArr.Free;
  end; 
end;

obj.addField('colors.0.details.*', 'code');
Sign up to request clarification or add additional context in comments.

4 Comments

Remyyyyyy i love you, thank you so much, YOU ARE MASTER. THANK YOU THANK YOU THANK YOU. I am working on it more than 2 days. I changed last line to this : TJSONObject(pJSONVal).AddPair(TJSONPair.Create(Field, '')); then works. THANK YOU SO MUCH. YOU ARE A HERO.
@Nevermore there is no need to create a TJSONPair manually when adding a string field, as TJSONObject.AddPair() has an overload that takes 2 strings as input.
i am using DbXJSon and if i try without create TJSONPair, it does not. Also Items doesnot work for me i use Get(Index). And i use Size instead of Array.Count May be the reason is DbxJson i dont know. I am using XE5. But for now, it is working and i have difficulty in believing it. Really really THANK YOU. I'm about to cry. I thought i will never work.
@Nevermore that is relevant information that belonged in your question. Details matter. In any case, the same AddPair() overload exists in XE5. You are right about TJSONArray.Items missing though, so I have adjusted my answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.