0

I'm using Delphi XE2, I have this json struct to create:

[
    {
        "Email": "laura@yyyy.com",
        "MobileNumber": "",
        "MobilePrefix": "",
        "Name": "Laura",
        "Fields": [
            {
                "Description": "nominativo",
                "Id": "1",
                "Value": "Laura"
            },
            {
                "Description": "societa",
                "Id": "2",
                "Value": ""
            },
            {
                "Description": "idcontatto",
                "Id": "3",
                "Value": "0"
            }
        ]
    },
    {
        "Email": "paolo@xxxx.com",
        "MobileNumber": "",
        "MobilePrefix": "",
        "Name": "Paolo",
        "Fields": [
            {
                "Description": "nominativo",
                "Id": "1",
                "Value": "Paolo"
            },
            {
                "Description": "societa",
                "Id": "2",
                "Value": ""
            },
            {
                "Description": "idcontatto",
                "Id": "3",
                "Value": "1"
            }
        ]
    }
]

I did several tests with superobject but have not yet arrived at the correct result because I get the first element of the array equal to the second. My difficulty is in iteration and optimization. This is the code I'm working on:.

json := TSuperObject.Create;    
jsonArray:= TSuperObject.Create(stArray);

json.S['Email'] := 'laura@yyyy.com';    
json.S['MobileNumber'] := '';    
json.S['MobilePrefix'] := '';    
json.S['Name'] := 'Laura';    
json['Fields'] := SA([]);    
json_Fields:=SO;    
json_Fields.S['Description']:='nominativo';    
json_Fields.S['Id']:='1';    
json_Fields.S['Value']:='Laura';    
json.A['Fields'].Add(json_Fields);    
json_Fields:=SO;    
json_Fields.S['Description']:='societa';    
json_Fields.S['Id']:='2';    
json_Fields.S['Value']:='';    
json.A['Fields'].Add(json_Fields);    
//......other fields    
JsonArray.AsArray.Add(json);

json.S['Email'] := 'paolo@xxxx.com';    
json.S['MobileNumber'] := '';    
json.S['MobilePrefix'] := '';    
json.S['Name'] := 'Paolo';    
json['Fields'] := SA([]);    
json_Fields:=SO;    
json_Fields.S['Description']:='nominativo';    
json_Fields.S['Id']:='1';    
json_Fields.S['Value']:='Paolo';    
json.A['Fields'].Add(json_Fields);    
json_Fields:=SO;    
json_Fields.S['Description']:='societa';    
json_Fields.S['Id']:='2';    
json_Fields.S['Value']:='';    
json.A['Fields'].Add(json_Fields);    
//......other fields    
JsonArray.AsArray.Add(json);

jsonArray.SaveTo('json_mu.txt');    
Jan Doggen
  • 8,799
  • 13
  • 70
  • 144

2 Answers2

2

You can use our record-based JSON serialization.

First you define a record, containing the expected data item, and a corresponding dynamic array:

type
  TMyRecord: record
    Name: string;
    Email: string;
    MobileNumber: string;
    MobilePrefix: string;
    Fields: array of record
      Id: integer;
      Description: string;
      Value: string;
    end;
  end;
  TMyRecordDynArray = array of TMyRecord;

Then you register the record content:

const // text description of the record layout
  __TMyRecord = 'Name,Email,MobileNumber,MobilePrefix string '+
    'Fields[Id integer Description,Value string]';

  ...

  TTextWriter.RegisterCustomJSONSerializerFromText(
    TypeInfo(TMyRecord),__TMyRecord);

And you can load or save the data from / to JSON:

var values: TMyRecordDynArray;
    i: integer;

  DynArrayLoadJSON(values,pointer(StringToUTF8(text)),TypeInfo(TMyRecordDynArray));
  for i := 0 to high(values) do
    writeln('name: ',values[i].Name,' mobile: ',values[i].MobilePrefix,' fields count:',length(values[i].Fields));
  DynArraySaveJSON(values,TypeInfo(TMyRecordDynArray));

This JSON serializer will use less memory than alternatives, will probably be faster, and you have compile-time check of the property names.

This is available from Delphi 6 up to XE5.

Edit:

For instance, to populate the array:

var R: TMyRecordDynArray;

SetLength(R,2);
with R[0] do begin
  Email := 'laura@yyyy.com';
  Name := 'Laura';
  Setlength(Fields,3);
  Fields[0].Description := 'nominativo';
  Fields[0].Id := 1;
  Fields[0].Value := 'Laura';
  Fields[1].Description := 'societa';
  Fields[1].Id := 2;
  Fields[2].Description := 'idcontatto';
  Fields[2].Id := 3;
  Fields[2].Value := '0';
end;
with R[1] do begin
  Email := 'paolo@xxxx.com';
  Name := 'Paolo';
  Setlength(Fields,3);
  Fields[0].Description := 'nominativo';
  Fields[0].Id := 1;
  Fields[0].Value := 'Paolo';
  Fields[1].Description := 'societa';
  Fields[1].Id := 2;
  Fields[2].Description := 'idcontatto';
  Fields[2].Id := 3;
  Fields[2].Value := '1';
end;

json := DynArraySaveJSON(R,TypeInfo(TMyRecordDynArray));

If you do not like the with statement, you can add R[0]. everywhere. Not the main point here, I suspect.

I hope it shows in light the benefit of using a compile-time structure instead of late-binding properties defined as text (as you are using SuperObject): you won't expect any problem at runtime, about the property names, or the general logic.

If you write json_Fields.S['DescriptioM']:='nominativo' you won't see any error in the IDE, whereas Fields[0].DescriptioM := 'nominativo' won't compile.

If you want to change a property name, you can refactor it in the IDE without any chance of forgetting one place in your code - it won't compile.

If your business code use high-level Delphi structures, you will need to write manually some error-prone code to convert those high level values to/from JSON, using most alternate librairies. Whereas with a solution like this, you can define your own value objects are record, even add some methods within the type definition, and use it directly in your business code, without wondering about the implementation details of the JSON persistence layer. In short: do you want to add a dependency to your JSON library in your business code? Sounds like breaking some principles we try to follow when defining Domain-Driven Design code.

Also compare with the code you added in your answer, about the readability and maintainability of your code.

And also the benefit of having all undefined string fields initialized with '' by default (as with any dynamic array).

Edit2:

As Jan wrote in his own answer you can use SuperObject to serialize record and dynamic arrays directly, as with our classes. So if you want ot use SuperObject, my advice is to use this feature, and work with Delphi high-level types. Note that SuperObject serialization will probably be slower than the one in our unit, and won't work with older versions of Delphi (whereas our unit works with Delphi 6/7 e.g.).

Community
  • 1
  • 1
Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
  • Thanks a lot for your suggestion but I'd rather use superobject at the moment. – user3319069 Feb 21 '14 at 10:10
  • @user3319069 I understand. But you were saying in your initial request (before edition, and addition of your code) that you tried several JSON libraries, so I just proposed a working alternative. See my edit of the answer, and perhaps you may find out the benefit of using a high-level Delphi structure instead of late-binding properties. – Arnaud Bouchez Feb 21 '14 at 15:32
1

Using SuperObject:

Define Delphi data structures to work with instead of manually building up your JSON structure, then use ToJSON to convert a Delphi object to a JSON structure:

Uses SuperObject;

type
   FieldRec = record
      ID: Integer;
      Description,
      Value: String;
   end;
   FieldArr = Array of FieldRec;
   BaseRec = record
      Fields: FieldArr;
   end;
   BaseArr = Array of BaseRec;

   OutputObject = class
      OutputData: BaseArr;
   end;

procedure TFrmAnotherJSONExample.FormShow(Sender: TObject);
var
   sObj: ISuperObject;
   lFieldArr: FieldArr;
   lBaseArr : BaseArr;
   lOutputObject: OutputObject;
begin
  SetLength(lBaseArr,2);
  SetLength(lFieldArr,3);
  for i := 0 to 2 do
  begin
     lFieldArr[i].ID := 10*i;
     lFieldArr[i].Description := 'Description' + IntToStr(lFieldArr[0].ID);
     lFieldArr[i].Value := 'Name' + IntToStr(lFieldArr[0].ID);
  end;
  lBaseArr[0].Fields := lFieldArr;
  for i := 0 to 2 do
  begin
     lFieldArr[i].ID := 100*i;
     lFieldArr[i].Description := 'Description' + IntToStr(lFieldArr[0].ID);
     lFieldArr[i].Value := 'Name' + IntToStr(lFieldArr[0].ID);
  end;
  lBaseArr[1].Fields := lFieldArr;
  lOutputObject := OutputObject.Create;
  lOutputObject.OutputData := lBaseArr;
  sObj := lOutputObject.ToJSON;
  lOutputObject.Free;
  Memo1.Lines.Add(sObj.AsJSON(true));
end;

Output of the above:

{
 "OutputData": [
  {
   "Fields": [
    {
     "Description": "Description0",
     "ID": 0,
     "Value": "Name0"
    },{
     "Description": "Description0",
     "ID": 100,
     "Value": "Name0"
    },{
     "Description": "Description0",
     "ID": 200,
     "Value": "Name0"
    }]
  },
 {
   "Fields": [
    {
     "Description": "Description0",
     "ID": 0,
     "Value": "Name0"
    },{
     "Description": "Description0",
     "ID": 100,
     "Value": "Name0"
    },{
     "Description": "Description0",
     "ID": 200,
     "Value": "Name0"
    }]
  }]
}

This way you get named arrays, I'm assuming that is fine (and I would recommend it).
Also, it has the outer {} which define the JSON object.

Added: Right after posting I see that I have forgotten to fill the Email, MobileNumber etc fields, but that's trivial now and I leave that as an exercise to the reader ;-)

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144