Jump to content
Sign in to follow this  
Lars Fosdal

TJson array conversion?

Recommended Posts

I unabashedly summon the wisdom of @Uwe Raabe¬†and hope¬†he has some answers for me ūüôā

I have a base class that wraps the to/from JSON conversion for me and also contain some other helper functions

type 
  TJsonElement = class
  public
    class function CreateFromJson<T: TJsonElement, constructor>(const aJson: string): T;
    class function LoadFromFile<T:TJsonElement, constructor>(const aFileName: String):T;
    class function PrettyFormat(const aJsonString: String; const AsHTML:Boolean = False; const UnEscapeJson:Boolean = False):String;
    function AsJsonString: string; 
  end;

 

And I am currently using TArray<T> to do lists, but it gets old writing helpers for each TArray<T> variation, so I'd like to do something like this and wrap a TArray to do all the chores of insert, add, clear, remove, delete, etc. once and for all 
I'd add these explicitly as methods of TJsonList.

type
  TJsonList<T: TJsonElement, constructor> = class(TJsonElement)
  type
    JArray = TArray<T>;
  private
    FItems: JArray;
    function GetItem(Index: Integer): T;
    procedure SetItem(Index: Integer; const Value: T);
  public
   function Add: T;
   function Add(const aItem: T): T;
   procedure Remove(const aItem: T);
  // etc etc
   property Items{Index:Integer]: T read GetItem write SetItem; default;
  end;

 

type
  TThing = class(TJsonElement)
  private
    Fprop: string;
  public
    property prop: string read Fprop write Fprop;
  end;
  
// current way
  TThingArray = TArray<TThing>;

  TArrayContainer = class(TJsonElement)
  private
    Fthings: TThingArray;
  public
    property things: TThingArray read Fthings write Fthings;
  end;
  
// new way
  TThingList = class(TJsonList<TThing>);
  
  TListContainer = class(TJsonElement)
  private
    Fthings: TThingList;
  public
    property things: TThingList read Fthings write Fthings;
  end;

 

So, what is the problem? 
Assume that the two objects have been created and a couple of TThing elements added.

TArrayContainer.ToJsonString will output

{
	"things": [{
		"prop": "A"
	}, {
		"prop": "B"
	}]
}


Without a converter/reverter, TListContainer.ToJsonString will output

{
	"things": {
		"items": [{
			"prop": "A"
		}, {
			"prop": "B"
		}]
	}
}

 

So,
Challenge 1: Can I make a converter/reverter that will not output things as an object, but hide items and simply output an array of TThing?

Challenge 2: Can I make the conversion override permanent, so that I don't have to use an attribute for every instance and descendant of TJsonList<T>?
 

 

Share this post


Link to post
2 hours ago, Lars Fosdal said:

Can I make a converter/reverter that will not output things as an object, but hide items and simply output an array of TThing?

So you want the output of "new way" be the same as in "current way"?

 

Can you provide a small but working example program producing both of the above outputs?

Share this post


Link to post
1 hour ago, Uwe Raabe said:

So you want the output of "new way" be the same as in "current way"?

 

Can you provide a small but working example program producing both of the above outputs?

 

Yes - that is the goal. Remain compatible with the original format, but have reusable code for lists.  In theory, it is probably better to have the internal type being a TObjectList<T> to get more functionality "for free".

The conversion would break if you add other properties to the TJsonList<T> class - but I can safeguard against that f.x. by sealing the class etc.

Test project attached (Note that the test classes does not do proper memory management on free).  
JsonListTest.dpr
JsonListTestType.pas
JsonListTestCase.pas

Output: 

TTestArray Original:
{
  "things":[
    {
      "prop":"A1"
    },
    {
      "prop":"B1"
    }
  ]
}
TTestArray Restored:
{
  "things":[
    {
      "prop":"A1"
    },
    {
      "prop":"B1"
    }
  ]
}

TTestList Original:
{
  "things":{
    "items":[
      {
        "prop":"A2"
      },
      {
        "prop":"B2"
      }
    ]
  }
}
TTestList Restored:
{
  "things":{
    "items":[
      {
        "prop":"A2"
      },
      {
        "prop":"B2"
      }
    ]
  }
}

Press Enter:


 

JsonListTestCase.zip

Share this post


Link to post
33 minutes ago, Lars Fosdal said:

The conversion would break if you add other properties to the TJsonList<T> class - but I can safeguard against that f.x. by sealing the class etc.

That would also require to change

type
  TThingList = class(TJsonList<TThing>);

into 

type
  TThingList = TJsonList<TThing>;

 

  • Thanks 1

Share this post


Link to post

Updated JsonListTestCase.pas and removed the test data initialization from the constructor ūüėõ¬†to make the restore test more credible.

JsonListTestCase.pas

Share this post


Link to post

Also - I guess a non-generic class inbetween will be needed for "is" checks.

i.e.
  TJsonElement = class abstract

     ...
  end;

TJsonList = class abstract (TJsonElement);

TJsonList<T> = sealed class (TJsonList)
  ...
end;

Share this post


Link to post

As far as I can see for now, that is not possible with the convenience methods of TJson. You will have to (de-)serialize the list in your own code. Currently I am not aware of any way to register a converter/reverter globally.

 

Unfortunately that requires to copy some code currently hidden in private methods.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

√ó