After watching Andrew MacNeill's Quasar and JSON: A Full Stack Experience for the DB Developer presentation at Virtual Fox Fest 2020, I was inspired to look at nfJSON, a VFX project by Marco Plaza. This cool project adds JSON support to VFP applications. The thing that tweaked my interest was the ability to convert a JSON string to a VFP object and vice versa with one line of code.
My first thought was using this for configuration settings. Just about every application needs configuration settings: it's better to read settings such as database connection information, file locations, email settings, etc. from a configuration source rather than hard-coding them into the app. I've used the Windows Registry, DBF files, INI, and XML files at various times but all of those require manually coding the reading and writing between the source and the VFP object(s) containing the settings. With nfJSON, it's one line of code.
I created a wrapper class called SFConfiguration. It only has three methods:
- Load returns an object with properties matching the name/value pairs in the JSON contained in the specified settings file. If the file doesn't exist or is empty (such as the first time the application is run), it calls GetDefaultSettings (described below) to get default settings.
- Save saves the properties of the specified settings object to the file specified in either the passed file name or the cSettingsFile property if a file name isn't passed.
- GetDefaultSettings returns JSON for default settings. You can use this one of two ways: subclass SFConfiguration and override GetDefaultSettings to return the desired JSON, or set the oSettings property to a settings object containing the default settings.
Here's an example of using this class to get email settings:
loConfig = createobject('SFConfiguration')
loConfig.cSettingsFile = 'email.json'
loConfig.oSettings = createobject('EmailSettings')
loSettings = loConfig.Load()
* loSettings contains the email settings for the user;
* if email.json doesn't exist, the settings in the
* EmailSettings class below are used as defaults.
* After the user enters the desired settings in some dialog,
* save them using:
loConfig.Save(loSettings)
define class EmailSettings as Custom
Email = 'dhennig@stonefield.com'
MailServer = 'mail.stonefield.com'
Port = 25
UseSSL = .F.
UserName = 'dhennig'
Password = 'mypw'
enddefine
Here's what the settings object looks like:
Here's what the saved JSON looks like:
Here's another example, this time using a subclass of SFConfiguration for the same thing:
loConfig = createobject('SFEmailConfiguration')
loConfig.cSettingsFile = 'email.json'
loSettings = loConfig.Load()
* loSettings contains the email settings for the user;
* if email.json doesn't exist, the settings in the
* SFEmailConfiguration class below are used as defaults.
define class SFEmailConfiguration as SFConfiguration
function GetDefaultSettings
text to lcSettings noshow
{
"email":"dhennig@stonefield.com",
"mailserver":"mailserver.stonefield.com",
"password":"mypw",
"port":25,
"username":"dhennig",
"usessl":false
}
endtext
return lcSettings
endfunc
enddefine
Here's the code for SFConfiguration. It requires nfJSONRead.prg and nfJSONCreate.prg, which you can get from the nfJSON GitHub repository:
define class SFConfiguration as Custom
cSettingsFile = ''
&& the name and path for the settings file
cErrorMessage = ''
&& the text of any error that occurs
oSettings = ''
&& a settings object
* Load the settings from the file specified in the parameter
* or in This.cSettingsFile and return a settings object. If
* the file doesn't exist (such as the first time we're called),
* a settings object containing default settings is returned.
function Load(tcSettingsFile)
local lcSettingsFile, ;
lcSettings, ;
loSettings, ;
loException as Exception
try
lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
if not empty(lcSettingsFile) and file(lcSettingsFile)
lcSettings = filetostr(lcSettingsFile)
endif not empty(lcSettingsFile) ...
if empty(lcSettings)
lcSettings = This.GetDefaultSettings()
endif empty(lcSettings)
loSettings = nfJSONRead(lcSettings)
This.cErrorMessage = ''
catch to loException
This.cErrorMessage = loException.Message
loSettings = NULL
endtry
This.oSettings = loSettings
return loSettings
endfunc
* Save the settings in the specified object to the file
* specified in the parameter or This.cSettingsFile.
function Save(toSettings, tcSettingsFile)
local lcSettingsFile, ;
lcSettings, ;
loException as Exception
lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
if not empty(lcSettingsFile)
try
lcSettings = nfJSONCreate(toSettings, .T.)
strtofile(lcSettings, lcSettingsFile)
This.cErrorMessage = ''
catch to loException
This.cErrorMessage = loException.Message
endtry
else
This.cErrorMessage = 'Settings file not specified.'
endif not empty(lcSettingsFile)
return empty(This.cErrorMessage)
endfunc
* Gets the default set of settings as a JSON string; override
* this in a subclass if necessary.
function GetDefaultSettings
local lcSettings
if vartype(This.oSettings) = 'O'
lcSettings = nfJSONCreate(This.oSettings, .T.)
else
lcSettings = '{"text":"some text"}'
endif vartype(This.oSettings) = 'O'
return lcSettings
endfunc
enddefine