Delphi XE7: How to change a JSON value using System.JSON (versus SuperObject)
Asked Answered
C

2

5

I need to load a JSON file, change a value and then write it back to disk.

This is easy using SuperObject, but how do I do the same thing using the System.JSON unit?

const
  PathToX = 'AllCategories.Category[0].subCategory[0].products[0].views.view[0].x';

var
  JsonFilename: string;

  JO: ISuperObject; // from the SuperObject unit
  JV: TJsonValue;   // from the System.Json unit

begin
  JsonFilename := ExtractFilePath(Application.ExeName)+'product.json');

  // Using the SuperObject unit:
  JO := SO(TFile.ReadAllText(JsonFilename));

  WriteLn('The old value of "x" is ', JO[PathToX].AsString);
  WriteLn('Changing value of x to "123"');
  JO.S[PathToX] := '123';   // Set the value of "x"
  WriteLn('The new value of "x" is ', JO[PathToX].AsString);

  // Now trying to do the same thing using the System.Json unit:
  JV := TJSONObject.ParseJsonValue(TFile.ReadAllText(JsonFilename));

  WriteLn('The old value of "x" is ', JV.GetValue<string>(PathToX));
  WriteLn('Changing value of x to "123"');
// Question: What code should go here to set the value of "x" using System.JSON ??? 
  WriteLn('The new value of "x" is ', JV.GetValue<string>(PathToX));

There doesn't seem to be a "SetValue" equivalent to the "GetValue" method in System.JSON.

Calaverite answered 29/10, 2015 at 23:42 Comment(0)
S
9

TJSONObject does support a path evaluator similar to SuperObject. So you will not have to manually drill into the JSON value tree one object at a time (though you certainly could if you wanted to).

However, the System.JSON classes are actually NOT designed for modifying existing data (believe it or not)! They are designed for parsing data, and creating new data. All of the JSON classes that represent simple values (integers, boolean, strings) are read-only. Fortunately, the TJSONPair class allows a value to be replaced, so you will have to take advantage of that.

Try something like this:

uses
  ..., System.JSON;

var
  JsonFilename: string;
  JV: TJSONValue;
  JO: TJSONObject;
  JoX: Integer;
  JoPair: TJSONPair;
begin
  JsonFilename := ExtractFilePath(Application.ExeName) + 'product.json';

  JV := TJSONObject.ParseJSONValue(TFile.ReadAllText(JsonFilename));
  if JV = nil then raise Exception.Create('Cannot parse file: ' + JsonFilename);
  try
    JO := JV as TJSONObject;

    JoX := JO.GetValue<Integer>('AllCategories.Category[0].subCategory[0].products[0].colors.color[0].views.view[0].x');
    WriteLn('The old value of "x" is ', JoX);

    WriteLn('Changing value of "x" to "123"');
    JoPair := JO.GetValue<TJSONObject>('AllCategories.Category[0].subCategory[0].products[0].colors.color[0].views.view[0]').Get('x');
    JoPair.JsonValue.Free;
    JoPair.JsonValue := TJSONNumber.Create(123);
    WriteLn('The new value of "x" is ', JoPair.JsonValue.Value);

    SaveAsDialog.FileName := JsonFilename;
    if SaveAsDialog.Execute then TFile.WriteAllText(SaveAsDialog.FileName, JO.ToJSON);
  finally
    JV.Free;
  end;
end;

Alternatively:

uses
  ..., System.JSON;

var
  JsonFilename: string;
  JV: TJSONValue;
  JO: TJSONObject;
  JoX: TJSONPair;
begin
  JsonFilename := ExtractFilePath(Application.ExeName) + 'product.json';

  JV := TJSONObject.ParseJSONValue(TFile.ReadAllText(JsonFilename));
  if JV = nil then raise Exception.Create('Cannot parse file: ' + JsonFilename);
  try
    JO := JV as TJSONObject;

    JoX := JO.GetValue<TJSONObject>('AllCategories.Category[0].subCategory[0].products[0].colors.color[0].views.view[0]').Get('x');
    WriteLn('The old value of "x" is ', JoX.JsonValue.Value);

    WriteLn('Changing value of "x" to "123"');
    JoX.JsonValue.Free;
    JoX.JsonValue := TJSONNumber.Create(123);
    WriteLn('The new value of "x" is ', JoX.JsonValue.Value);

    SaveAsDialog.FileName := JsonFilename;
    if SaveAsDialog.Execute then TFile.WriteAllText(SaveAsDialog.FileName, JO.ToJSON);
  finally
    JV.Free;
  end;
end;
Sweetheart answered 30/10, 2015 at 2:12 Comment(3)
Now, the reason why I'm looking for an alternative to SuperObject is because it messes up the order of the JSON pairs. I know that the JSON standard defines the pairs as an "unordered collection", but our JSON files are maintained partially by hand. After all, the whole point of JSON is that it is human readable.Calaverite
Some people here would get very angry with me if my little program would mess up their carefully handcrafted JSONs! So I am looking for an alternative to SuperObject, that support path's, and "getting" and "setting" values and also outputs "pretty" indented JSON, but WITHOUT messing up the order of the JSON objects. Do you know of any library that meets these requirements?Calaverite
How do I pretty-print JSON in Delphi?Sweetheart
G
0

The answer of Remy fails in Delphi 10.4 with an access violation at JO.ToJSON. Leaving out the JoX.JsonValue.Free fixes this. This works:

Var S := '{"Key1":"Foo","Key2":"Foo","Key3":"Foo"}';
Var JO := TJSONObject.ParseJSONValue(S) as TJSONObject;
Var JP := JO.Get('Key2');
// JP.JsonValue.Free; DO NOT DO THIS!
JP.JsonValue := TJSONString.Create('Bar');
S := JO.ToJSON;
JO.Free;

S now reads '{"Key1":"Foo","Key2":"Bar","Key3":"Foo"}', i.e. the value was replaced as intended and the order of the pairs remained unchanged. According to MadExcept, omitting the JP.JsonValue.Free did not leave a memory leak.

Grande answered 16/8, 2023 at 12:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.