CapeSoft.Com
Clarion Accessories
jFiles
Documentation
CapeSoft Logo

CapeSoft jFiles
Documentation

Download Latest Version JumpStart FAQ History Classes Methods
Installed Version Latest Version

Introduction

jFiles adds JSON support to Clarion. It contains classes that let you create, and consume data files formatted as JSON.

JSON (JavaScript Object Notation) is a formatted structure used for moving data between programs. It is more structured (and easier to deal with) than CSV but less verbose then XML. Thus it is especially popular as a format used to interchange data over the internet - either between a browser and a server, between a WebService and a client, or between a mobile device and a web server. The whole JSON specification is very simple, and is summarized at http://json.org/.

jFiles makes it really easy to move data between common Clarion structures (Table, Queue, View, Group) and JSON "strings". These strings could be in memory (suitable for being sent out over the internet) or stored as files on the disk.

Upgrading to jFiles 2

New Features

Changes

Upgrading from jFiles 1 should be very straight-forward.

The most likely issue you will encounter is that jFiles one provided both direct access to all properties, and GETter and SETter methods for accessing the properties.
In jFiles 2 the properties have "moved" so it is recommended to use the GET and SET methods for the properties instead. Strictly speaking this isn't an upgrade issue - jFiles 1 recommended the GETter and SETter - but jFiles 2 enforces this, so if you have been a bit tardy with your hand-code then you'll have a bit of work to do.

Fortunately fixing the code is very simple.  Where you have something like

json.TagCase = jf:CaseLower

This changes to

json.SetTagCase(jf:CaseLower)

and code which gets a value, say

c = json.TagCase

becomes

c = json.GetTagCase()

AddCopy method

Code upgrading from jFiles 1, and jFiles 2 prior to build 2.04, will want to review the behavior for any hand-code calls to the AddCopy method. This method changed behavior (twice, in 1.59 and 2.04) and from build 2.14 has an optional parameter to specify the desired behavior .

Requirements & Recommendations

jFiles requires StringTheory 3 .

If you are creating NetTalk Web Services, and you want to use jFiles to create and consume the JSON in those web services, then you need NetTalk build 8.42 or later.

Acknowledgment

jFiles is derived from code written by Dries Driessen. It is used here under license.

Features

Webinars

A number of webinars on using jFiles, and jFiles features exist. Here is an (incomplete) list of some of them;
Link Description
524 Creating and Consuming JSON using jFiles (part 1 - Creating)
525 Creating and Consuming JSON using jFiles (part 2 - Consuming)
529 jFiles 2 - some simpler strategies for creating and consuming complex structures
581 Using new JsonClarionClass to write Clarion code.

Jump Start

  1. Add the jFiles global extension to your application
  2. Go to a procedure where you want to create, or consume, JSON. Either;
    • Add a jFile Local Extension there to declare the object (and set the object name there) or
    • Declare the object manually in embed code like this;
      json  JSONClass
  3. Read the sections below to either Import JSON or Create JSON as you wish.
  4. Read the section on Deformatting incoming JSON, or Formatting outgoing JSON if you need to change the JSON format.

Adding jFiles to a Hand-Code Project

To add jFiles to a hand-coded project (with no APP and hence no Global Extension template) do the following;
  1. To your main module, add

    include('jFiles.Inc'),Once
    include('StringTheory.Inc'),Once
  2. Add the jFilesLinkMode and jFilesDllMode project defines to your project. Set

    jFilesLinkMode=>1
    StringTheoryLinkMode=>1
     
    if the class should be linked into the project, and

    jFilesDllMode=>1
    StringTheoryDllMode=>1

    if the object is exported from another DLL.
  3. Add the _ABCDllMode_=>0 and _ABCLinkMode_ =>1 defines to your project if they are not already there. These are needed for the critical section support that jFiles uses.
  4. When you want to declare an jFiles object in a procedure you can declare it as

    json  JSONClass


    If you wish to derive methods (see methods documentation) then declare it with the method (for example)

    json           class(JSONClass)
    AddQueueRecord   PROCEDURE (Long pFirst=0),Long,Proc,Virtual
                   End


    and then add the derived procedure (adding your own code)

    json.AddQueueRecord PROCEDURE (Long pFirst=0)
      Code
      parent.AddQueueRecord (pFirst)


    For a full list of method declarations see the jfiles.inc file.

Extended Name Attributes

All the Clarion structures (Tables, Queues and Groups) allow the fields in those structures to have a NAME attribute. This allows the JSON field name to be set to a specific value (and a specific case) which can be different from the label. If you are exporting field names that are mixed case, using jfiles 1, then you will already have used the External Name of the field (aka the NAME attribute) to set the name.

It's also possible to store additional information in this field, in a pipe ( | ) separated list of attributes. This means that when exporting, or importing, data a lot more information can be included in the structure, and jFiles 2 can make use of this information. Multiple attributes can be used at the same time.

For example, by including the field picture in the NAME attribute you can automatically have the data formatted in the JSON output.

Extended Name Attributes are a new concept, and support for them will grow with new releases. Here are the supported attributes so far;
Saving Loading Attribute Meaning Example
yes yes @Picture Outgoing JSON is formatted to this picture, Incoming JSON is deformatted to this picture. date   long, name('date | @D10')
yes no Type Outgoing structures can contain complex data types. Supported types are
StringTheory, File, Queue, View, JsonClass
str  &stringtheory, name('str | &StringTheory')
yes no & Outgoing structures can contain reference variables. notes  &string, name('notes | &String')
yes no StringTheoryJson,
StringJson
StringTheory objects, or Strings, with this type already contain valid JSON, and they are added into the structure "as is" with no extra JSON formatting or encoding. related  &StringTheory, name('related | StringTheoryJson')
yes no Rename(whatever),
JsonName(whatever)
Allow the name of the field to be different from the External Name in the JSON output id  long, name('id | jsonname(InvoiceNumber)'
yes no Boolean The JSON output will be a boolean value (true or false). paid  byte, name('paid |boolean')

Importing JSON

For the purposes of this section it is assumed that the JSON object is declared as follows;

json JSONClass

Loading from a JSON File into a Group, Queue or Table

Loading a JSON file into a structure is a single line of code.

json.Load(Structure, JsonFileName, <boundary>)

You can use a Table, Queue or Group using this approach.

If the JSON file contains named objects, then specify the name as the boundary parameter. If you omit this parameter, but the JSON you want to import is inside a named JSON object, then nothing will be imported.

The fields in the JSON file are matched to fields in your record structure. There are three properties which affect this matching. For more information on matching see the section on Field Matching Tips.

After the load is complete two methods will be available for your use;

GetRecordsInserted()
GetRecordsUpdated()


These methods return a counter of the records inserted (into the Table or Queue) and the number of records updated (in the Table). These counters are reset to zero by a call to the Start method.

When the Load method completes it will return either jf:Ok (0)  or jf:ERROR (-1). If jf:Error is returned then a description of the error is in the Error property. A call to the ErrorTrap method will have been made as well.

The use of a JSON Array (ie list) structure usually implies that the Clarion structure should also be a List, and equally the use of a JSON Object (not a list) structure implies a group, or value on the Clarion side. However jFiles automatically supports simple object into a List structure (the same as a list with one value) and also importing a List structure into a Clarion Group structure.

Loading from a JSON String into a Group, Queue or Table

Loading a JSON string into a structure is a single line of code. It's the same as for a table, but uses a StringTheory object instead of a file name.

str  StringTheory
  code
  json.Load(Structure, str, <boundary>)


This is the same as the Loading from a File method described above, except that the source is a StringTheory string, and not a file.

Loading a JSON String into the JSON object for processing

The JSONobject can be loaded from a string or file using one of these methods;

json.LoadString(StringTheory)

or

json.LoadFile(Filename)

Once the JSON text has been loaded into the object it is automatically parsed, and then you can directly inspect the contents of the JSON.

jsonItem  &JSONClass
str  StringTheory
  code
  str.SetValue('json text is here')
  json.LoadString(str)


Remember that all JSON files are just a collection of items. Each item in turn can be a collection of more items. Once the string is loaded into the JSON object the Records method returns the number of items at the top level;

x = json.Records()

Like a queue you can loop through the items in the object using the Get method

loop x = 1 to json.records()
  jsonItem &= json.Get(x)
end


You can check the name of the item using the Name method

loop x = 1 to json.records()
  jsonItem &= json.Get(x)
  if jsonItem.Name() = 'Customers'
    ! do something
  end
end


and of course you can get the value using the GetValue method. So;

loop x = 1 to json.records()
  jsonItem &= json.Get(x)
  if jsonItem.Name() = 'Customers'
    somevalue = jsonItem.GetValue()
  end
end


an alternative to looping through the items in the object is to use the GetByName method.

jsonItem &= json.GetByName('customers')

You can test if you got something back by checking that jsonItem is not NULL. This is important - using a NULL object will result in a GPF

If not jsonItem &= Null

Once you have a specific item, you can load this into a structure;

jsonItem.Load(structure)

The field names in your JSON will need to match the fieldnames in your structure. See Field Matching Tips for hits on making the matching process better.

Putting the code together it looks something like this;

jsonItem &= json.GetByName('customers')
If not jsonItem &= Null
  jsonItem.Load(structure)
End


If you have a specific field in the JSON you can extract it using the GetValueByName method

somestring = json.GetValuebyName('surname')

Deformatting incoming JSON Values

By default the contents of a JSON field will be copied into your Clarion field during the LOAD. However the format of the JSON field may not be the format you wish to store in your Clarion field. For example an incoming date, formatted as yyyy/mm/dd may need to be deformatted in order to store it in a Clarion LONG field.

This can be done by adding the desired Picture to the NAME attribute of the field. For example;

InvoiceQueue Queue
Date Long,name('Date | @D3')
End


All StringTheory Extended Pictures are supported. For more information on the name attribute see Extended Name Attributes.

jFiles 1

The process above is considerably simpler than the one below, and should be used wherever possible. If it is not possible, or you are reading old code (and trying to understand it) then the jFiles 1 method is described below.

To do this a method is provided;

json            Class(jsonClass)
DeformatValue     Procedure (String pName, String pValue),STRING,VIRTUAL
                End

Please note that the name being passed in here, in the pName parameter, is the JSON field name, not the Clarion field name. Using this name as the identifier it is possible to create a Case statement before the parent call, deformatting the value as required. For example;

case pName
of 'DATUM'
  Return deformat(pValue,'@d1')
of 'TIME'
  Return deformat(pValue,'@t4')
end

Filtering Records when Loading

When loading into a Table or Queue it can be useful to filter out records which are not desired.
This is done by embedding code into the ValidateRecord method.

json             Class(jsonClass)
ValidateRecord     Procedure(),Long,Proc,Virtual
                 End


If (your code in) this method returns jf:filtered then the record is not added to the Table or Queue and the next record is processed. If (your code in) this method returns jf:outofrange then the import is considered complete, and no further records are loaded. If (your code in)this method returns jf:ok then the record is added to the table or queue.

When this method is called the table record, or queue record, has been primed with the record that is about to be written.

Example

json.ValidateRecord Procedure ()
  Code
  If inv:date < date(1,1,2018) then Return jf:filtered.
  Return jf:ok


Match By Number

While JSON is commonly formatted as "name value pairs", it doesn't have to be this way. It could just be a collection of arrays, with no names at all. For example say this is the contents of the a.json file;

[
    [45,80],
    [30,85],
    [16,4]
]


To load this into a structure it's clearly not possible to match the json to field names in the structure, rather it needs to import based on the position of the value. This can be done by setting the MatchByNumber property to true. For example;

aq QUEUE
tem string(255)
pressure string(255)
end

code
  json.start()
  json.SetMatchByNumber(true)
  json.load(aq,'a.json')

Loading a StringTheory object within a Group or Queue

Consider this JSON structure;

{
    "product":"NetTalk",
    "description":"A really long description goes here",
    "installfile":"base64encoded file install - could be very large"
}


In this situation one (or more) of the items in the group are of indeterminate length. This is clearly a situation for using a StringTheory object.The Clarion structure should look like this;

ProductGroup  Group
Product         String(100),name('product')
Description     &StringTheory,name('description | stringtheory')
InstallFile     &
StringTheory,name('installfile | stringtheory')
              End


Note the use of the Extended Name Attribute.
You are free to create the StringTheory objects - or you can leave them as null (they will be automatically created).
However whichever approach you take the objects MUST be disposed of after you are done with them.


Example

ProductGroup.Description &= New StringTheory ! this line is optional
ProductGroup.InstallFile &= New StringTheory
! this line is optional

json.Start()
json.SetTagCase(jf:CaseAsIs)
json.Load(ProductGroup,'somefile.json')

! do whatever you like
If not ProductGroup.Description &= Null  ! be sure to test the pointer before using it.
  ProductGroup.Description.Trace() 
End

! must tidy up the pointers at the end.
json.DisposeGroup(ProductGroup)
! This line is NOT optional

Tip: As with any pointer in a structure, it may end up being NULL if the incoming JSON does not include this field. So you should ALWAYS test the pointer before using it. For example;


Loading a Queue within a Group

Consider this JSON structure;

{
    "error": "validation_error",
    "cause": [
        { "cause_id": 369,
          "message": "some message"
        },
        { "cause_id": 121,
          "message": "some other message"
        },
    ]
}

It's clearly a group (since the outside brackets are {}) but it contains a list (cause [] ), or, in other words, a queue inside a group.

In Clarion this group can be declared as

CauseQueueType     Queue,Type
cause_id             long,name('cause_id')
message              string(255),name('message')
                   End

ErrorGroup         Group
Error                String(100) , Name('Error')
Cause                &CauseQueueType, Name('cause | queue')
                   End


Note the use of a queue type declaration inside a group declaration here. Note the use of the Extended Name Attribute.

Before using this group, a new queue must be assigned;

  ErrorGroup.Cause &= new CauseQueueType

then load it

  json.SetTagCase(jF:CaseAny)
  json.Load(ErrorGroup, 'whatever.json')


then you can access the queue as any normal queue. For example

  GET(ErrorGroup.Cause,1)
  Json.Trace(ErrorGroup.Cause.Message)


After using it remember to dispose the queue

Dispose(ErrorGroup.Cause)

Loading a Queue within a Queue

Consider this JSON structure;

[{
    "Id": 60,
    "Data": {
       
"TotalPaymentAmount": 90.63,
        "Discounts": [{
           
"DiscountType": "DASH",
            "Amount": 136.27
        }, {
           
"DiscountType": "LOTS",
            "Amount": 210.27
        }]
    }
}, {
    "Id": 61,
    "Data": {
       
"TotalPaymentAmount": 90.63,
        "Discounts": [{
           
"DiscountType": "DASH",
            "Amount": 325.27
        }, {
           
"DiscountType": "LOTS",
            "Amount": 499.27
        }]
    }
}, {
    "Id": 63,
    "Data": {
        "TotalPaymentAmount": 90.63,
        "Discounts": {
            "DiscountType": "SPECIAL",
            "Amount": 525.27
        }
    }
}]


This is a List, containing multiple records.  Inside each record is a group (Data) and inside each group is a queue of discounts.

The (simplified) Clarion version of this structure would ideally look like this (but as you'll see this is not allowed)

Messages            Queue
Id                    Long
Data                  Group
TotalPaymentAmount      Decimal(10,2)
Discounts               Queue
Amount                    Decimal(10,2)
                        End
                      End
                    End


Clarion does however not allow queues to be declared inside groups, or inside other queues. what you are allowed are references to queues. This can be tricky to work with to make sure there are no memory leaks - fortunately jFiles includes methods to make this safe.

The data declaration looks like this;

DiscountsQueueType  Queue,Type
DiscountType          String(20),name('DiscountType')
Amount                Decimal(10,2),name('Amount')
                    End    

Messages            Queue
Id                    Long,name('Id')
Data                  Group,name('Data')
TotalPaymentAmount      Decimal(10,2),name('TotalPaymentsAmount')
Discounts               &DiscountsQueueType,name('Discounts | queue')
                      End
                    End



Note the use of the Extended Name Attribute in the declaration of the Discounts field.

Because the DiscountsQueueType is in scope in your code, it's necessary for your code to NEW this type. This is done in the NewPointer method. So the declaration looks like this;

json         class(jsonClass)
NewPointer     procedure(String pColumnName),Derived
             end


And the method itself looks like this;

json.NewPointer Procedure(String pColumnName)
code
  Case pColumnName
  Of 'Discounts'  ! note this is teh json tag name, not the label. case sensitive.
  MessagesQ.Data.DiscountsQ &= NEW DiscountsQueueType
  End


that's all there is to it. The rest of the load is as normal;

  json.Start()
  json.SetTagCase(jf:CaseAsIs) ! CaseAsIs uses the NAME attribute, not the LABEL
  json.Load(MessagesQ,str)



Note: Although jFiles includes code to support &SomeGroupType  in the class, the Clarion NEW command does not support Groupp Types, so effectively &GROUPs in Queues or Groups are not supported.

jFiles 1

An alternative approach is to use two Queues.
(This approach is obsolete now, but is included here to understand code that already exists)

MessagesQ           Queue
Id                    Long,name('Id')
Data                  Group,name('Data')
TotalPaymentAmount      Decimal(10,2),name('TotalPaymentAmount')
                      End
                    End

DiscountsQ          Queue 
MessageId             Long
DiscountType          String(20),name('DiscountType')
Amount                Decimal(10,2),name('Amount')
                    End

All the messages go in one queue, and all the discounts go in another. The MessageID serves to determine which discounts belong in which queue.

The code to populate these two queues looks like this;

json              Class(JSONClass)
AddQueueRecord      Procedure (Long pFirst=0),Long,Proc,Virtual
                  End
oneMess           &jsonClass
node              &jsonClass
MessageId         long

  CODE
  Free(MessagesQ)
  Free(DiscountsQ)

  ! usual setup
  json.Start()
  json.SetFreeQueueBeforeLoad(False)
! will be needed to add to the DiscountsQ multiple times.
  json.SetRemovePrefix(True)
  json.SetReplaceColons(True)
  json.SetTagCase(jf:CaseAsIs)  !
CaseAsIs uses the NAME attribute, not the LABEL
  json.LoadString(jsonStr)
  json.Load(MessagesQ)  ! load the messages Queue first

 
! now need to load the discount queue. Do this by looping through the message nodes, isolating
  ! the ID and Discounts fields, and using them appropriately.

  Loop x = 1 to json.records()
    oneMess &= json.Get(x)
   
! need to get the ID field for the child queue
    node &= onemess.GetByName('Id')
    if not node &= NULL
      MessageId = node.GetValue()
! Saving the MessageId for later use when adding to the DiscountQ
      ! now get the discounts
      node &= oneMess.GetByName('Discounts',2)
! Discounts is in Data so allow search 2 levels deep
      if not node &= NULL
        node.Load(DiscountsQ)
! simple load into the DiscountsQ. note setting of FreeQueueBeforeLoad above
      end
    end
  end

!-------------------------------------------------------------------------------------
json.AddQueueRecord Procedure (Long pFirst=0)
Q   &Queue
  CODE
  Q &= self.Q
  If Q &= DiscountsQ
    DiscountsQ.MessageId = MessageId
! Prime the linking field before the queue record is added
  End
  Parent.AddQueueRecord(pFirst)

As you can see in the above approach the AddQueueRecord is overridden so that the extra field in the DiscountsQ is properly primed.

Aside: You may notice that the call to load the DiscountsQ uses the node object ( node.Load(DiscountsQ) ) but the object being overridden is the json object. Usually this would mean the code would not run, however jFiles automatically manages this, as node is a reference to a jFiles object inside the object called json, code in the json object automatically applies to the nodes as well.

Importing Recursive Nodes

Consider the following json;

[{
    "id": 1,
    "Parent": 0,
    "name": "John Smith",
    "children": [{
        "id": 2,
        "Parent": 1,
        "name": "Sally Smith"
     },{
        "id": 3,
        "Parent": 1,
        "name": "Teresa Smith"
    }]
}]

At first glance this looks like a list, but it's not. It's a collection of nodes, related to each other, in a tree like pattern. The parent and child notes however do have a common structure, which suggests this could be loaded into a queue.

TestQ         QUEUE 
id              LONG,NAME('id')
Parent          LONG,NAME('Parent') 
name            STRING(100),NAME('name') 
              END
 

To import a node structure into a list structure requires the engine to walk through the nodes, adding each one to the queue. This is done using the LoadNodes method.

  json.start()
  json.SetTagCase(jF:CaseAsIs)
  json.LoadFile('whatever.json')
  json.LoadNodes(TestQ,'children')


One advantage of the JSON above is that it contains a parent field, where every child node explicitly links to its parent. In many cases though this is not the case. Consider this JSON

[{
    "id": 1,
    "name": "John Smith",
    "children": [{
        "id": 2,
        "name": "Sally Smith"
     },{
        "id": 3,
        "name": "Teresa Smith"
    }]
}]


Now the identity of the parent is determined by the location of the node in the tree. If we move this into a queue then the location is lost.  The above structure suggests a queue like this;

TestQ         QUEUE 
id              LONG,NAME('id')
name            STRING(100),NAME('name') 
              END


Since the location is being lost an additional field to store the "parentId" is required.

TestQ         QUEUE 
id              LONG,NAME('id')
name            STRING(100),NAME('name') 
ParentID        LONG 
              END



Then in the call to LoadNodes this field, and the name of the identifying field, must be included.

  json.start()
  json.SetTagCase(jF:CaseAsIs)
  json.LoadFile('whatever.json')
  json.LoadNodes(TestQ,'children',TestQ.ParentID,'id')


The above code tells jFiles to use the ID field of one node as the ParentID of all child nodes.

Pointers

For known pointer types see;

The following approach in this section should only be for other kinds of pointers.

Consider a queue or group structure which contains a reference. For example;

SomeQueue   Queue
Date          Long
Time          Long
Notes         &Something
            End


When data is moved into the Notes field then a more complicated assignment needs to take place (for each incoming record in the JSON).

In order to make this possible code needs to be added to the AssignField and GetColumnType methods.

json          Class(jsonClass)
GetColumnType   Procedure (*Group pGroup, *Cstring pColumnName),Long,VIRTUAL
AssignField     Procedure (*Group pGroup, *Cstring pColumnName, JSONClass pJson),VIRTUAL
              End


The first thing to do is tell the system that a complex assignment needs to take place for a specific field. This is done in the GetColumnType method. In this example;

json.GetColumnType Procedure (*Group pGroup, *Cstring pColumnName)
  CODE
  Case pColumnName
  of 'Notes'
    return json:Reference
  end


You are responsible for the assignment. The assignment is done as follows:

json.AssignField Procedure (*Group pGroup, *Cstring pColumnName, JSONClass pJson)
  CODE
  Case pColumnName
  of 'Notes'
    SomeQueue.Notes &= NEW(Something)
    SomeQueue.Notes = pJson.GetValue()
  End
  Parent.AssignField(pGroup,pColumnName,pJson)


As you can see in the above code, the column name is tested, and if it is the reference column (Notes) then a SomeThing object is created, and the value is placed in there.

This is done for each record in the queue. This means that you need to be very careful when deleting records from the queue and when freeing the queue. And the queue MUST be correctly emptied before ending the procedure or a memory leak will occur.

For example;

FreeSomeQueue Routine
  loop while records(q:Product)
    Get(q:Product,1)
    Dispose(SomeQueue.Notes)
    Delete(q:Product)
  End


Detecting Omitted and NULL values

When importing into a structure (Group, Queue or Table), each field in the structure is primed from the incoming JSON. This works perfectly if the JSON contains a field with the same name.

If the field does not exist in the incoming JSON then the field in the structure is cleared, to a blank string or a zero value.
If the field does exist, but the value is set as null, then again the field is cleared or set to zero.

This approach is convenient and simple, but does make it difficult when you are working later on with the structure (perhaps a group or queue) to identify fields which were explicitly set to blank as distinct from fields which were omitted, as distinct from fields set to null.

To overcome this problem jFiles allows you to set specific values for omitted strings or numbers, and specific values for null strings and null numbers. While this doesn't necessarily solve the problem in all cases (you still need to have some values the user cannot use) it will be suitable in most cases.

For example;

json.Start()
json.SetTagCase(jf:CaseAsIs)

json.SetWriteOmittedString(True)
json.SetOmittedStringDefaultValue('//omitted//')
json.SetWriteOmittedNumeric(true)
json.SetOmittedNumericDefaultValue(2415919000)

json.SetNullStringValue('null')
json.SetNullNumericValue(2415919001)

json.Load(structure,'a.json')


Note that you would need to parse, and manage these values in your structure (group, queue, table) but at least you can tell that they have been omitted.

The properties for omitted and null are unrelated, you can use one set without the other set if you like.

Loading a JSON List into a Group

Typically if you have an incoming List of data you would load this into a matching Queue on the program site. It is however possible to use a Group structure, and have jFiles load that group structure, even if the incoming JSON contains a List structure.

For example;

Settings   Group
Server       String(255)
Port         Long
           End


this would usually expect JSON like this

{
    "Server":"www.capesoft.com",
    "Port":80
}


However the incoming JSON may occasionally be a list, like this

[{
        "Server":"https://www.capesoft.com",
        "Port":80
    },
    {
        "Server":"https://www.capesoft.com",
          "Port":443
    }
]


If the Load method was called with the Group as the destination structure then each item in turn will be loaded into the group, resulting in the last record in the JSON being left in the group when the Load completes.

For each record loaded into the group the ValidateRecord method is called. So you can embed code in here if you want to iterate through the list. If the method then returns jf:StopHere then the loop will terminate and the Group will be left with the current contents.

json             Class(jsonClass)
ValidateRecord     Procedure(),Long,Proc,Virtual
                 End

json.ValidateRecord Procedure ()
  Code
  If inv:date < date(1,1,2018) then Return jf:filtered.
  Return jf:ok

High-Speed Importing to Tables

Importing JSON to a table is fast, but does take time. One of the techniques for speeding up imports is to surround the imports with a LOGOUT / COMMIT statement.

The price of this approach though is that the tables are locked for the duration of the import. This means that other users won't be able to write to the tables (and may not be able to read from the tables) while the import is underway. So the choice is fast imports, with possible user blocking, or slower imports, but with no user blocking.

A property exists to turn this feature on (it is off by default).

json.SetLogoutOnImport(x)

Where x is either 1, or 2. If x is 1, then the Logout is attempted. If it fails, the import will continue anyway, but as a normal import, not a logout/commit import. If it is set to 2, and the logout fails, then the import is abandoned, and the method returns JF:ERROR.

For maximum speed it is recommended to commit the transaction every y records. A good value for y is "several thousand" and so y defaults to 5000. you can override this before the import by calling

json.SetFlushEvery(6000)

Setting it to 0 bypasses this feature, and only commits the transaction at the end of the entire import.

One side effect of FlushEvery is that a new transaction is started every y records. This means is the original Logout failed (and x is 1), then it will be retried after y records. So even if the original logout fails, it may logout at a later part of the import.

Creating JSON

For the purposes of this section it is assumed that the JSON object is declared as follows;

json JSONClass

Reusing a JSON object

If you reuse a json object multiple times then properties set in one use may inadvertently cascade to the next use. To "clean" an object so that it starts fresh, call the Start method. For example;

json.Start()

This will reset the internal properties back to their default values.

Saving a Clarion structure to a JSON File on disk

The simplest way to create a JSON file is simply to use the .Save method and an existing Clarion structure.

json.Save(Structure,<FileName>, <boundary>, <format>, <compressed>)

For example

json.Start()
json.Save(CustomerTable,'.\customer.json')


or

json.Start()
json.Save(CustomerTable,'.\customer.json','Customers')


or

json.Start()
json.Save(CustomerTable,'.\customer.json','',json:Object)


You can use a Group, Queue, View or File as the structure holding the data being saved.

The method returns 0 if successful, non-zero otherwise.

The boundary parameter allows you to "name" the records. For example, if the boundary parameter is omitted the JSON is

[ { record }, {record} , ... {record} ]

If the boundary is included then the JSON becomes

{ "boundary" : [ { record }, {record} , ... {record} ] }

The Format property determines if the output is formatted to human-readable or if all formatting is removed (to make the file a bit smaller). If omitted it defaults to true - meaning that the output is human readable. This is recommended, especially while developing as it makes understanding the JSON and debugging your code a lot easier.

If the Compressed parameter is omitted, then the default value is false. If the Compressed parameter is set to true then the file will be gzip compressed before writing it to the disk.

If the FileName parameter is omitted, or blank, then the json object will be populated with the file, but no disk write will take place. You can then save the object to a StringTheory object, or to a File, or use it in a collection later on.

Saving a Clarion structure to a JSON String in Memory

json.Save(Structure, StringTheory, <boundary>, <format>)

This is the same as saving the JSON to a File, except that the second parameter is a StringTheory object not a string.
For example;

str  StringTheory
  Code
  json.Start()
  json.Save(CustomerTable,str)

For explanation of the Boundary and Format parameters see the section above.

Constructing JSON Manually

In some cases constructing the correct Clarion structure may be difficult, or the structure itself may not be known at compile time.  In these situations you can use the low-level Add method to simply construct the JSON manually.

The Add method takes 3 parameters, the name, the value, and the type of the item. In turn it returns a pointer to the node created. Using this pointer allows you to embed inside nodes as you create them. For example;

{
  "company" : "capesoft",
  "location" : "cape town",
  "phone" : "087 828 0123",
  "product" : [
    { "name" : "jfiles" },
    { "quality" : "great" },
    { "sales" : "strong" }
  ]
}

In the above JSON there is a simply group structure, followed by a list containing a variable number of name/value pairs.
The code to create the above could be written as;

Json JSONClass
Product &JSONClass
KeyValue &JSONClass

code
  json.start()
  json.add('company','capesoft')
  json.add('location','cape town')
  json.add('phone','087 828 0123')
  Product &= json.add('product','', json:Array)
  KeyValue &= Product.add('','',json:Object)
  KeyValue.add('name','jfiles')
  KeyValue &= Product.add('','',json:Object)
  KeyValue.add('quality','great')
  KeyValue &= Product.add('','',json:Object)
  KeyValue.add('sales','strong')


Of course this is just an example. Using the Add method, and the fact that it returns the node added, allow you to construct any JSON you like.

That said, this method can be tedious, and making use of Clarion structures is often easier to manage in the long run.

Storing Multiple Items in a JSON object

The Save methods described above are perfect for creating a simple JSON structure based on a simple Clarion Data Type. However there are times when you will need to create a single JSON object which contains multiple different elements (known as a Collection.)

collection &JSONClass

The collection is created using the CreateCollection method.

collection &= json.CreateCollection(<boundary>)

If the boundary is omitted then a default boundary ("subSection") will be used.


[Aside: You do not need to dispose the Collection object - that will be done for you when the json object disposes.]

You can then use the Append method to add items to the collection. There are a number of forms of the Append Method.

Append (File, <Boundary>)
Append (Queue, <Boundary>)
Append (View, <Boundary>)
Append (Group, <Boundary>)


As with the Save methods the Boundary parameter is optional and can be omitted. If the parameter is omitted then a default object name will be used.

You can also

Append (Name, Value, <Type>)

to add a single name value pair to the collection. The type is the JSON type of the Value. It should be one of

json:String   EQUATE(1)
json:Numeric  EQUATE(2)
json:Object   EQUATE(3)
json:Array    EQUATE(4)
json:Boolean  EQUATE(5)
json:Nil      EQUATE(6)


if the Type parameter is omitted then the default json:string is used.

Here is a complete example;

json        JSONClass
collection  &JSONClass
  code
  json.Start()
  collection &= json.CreateCollection('Collection')
  collection.Append(Customer,'Customers')
  collection.Append(Queue:Browse:1)
  collection.Append(MemoView)


Once you have created your collection you can save it to Disk or String using the techniques described below.

Saving a JSON Object to Disk

After you have constructed the JSON object to your satisfaction, you may want to store it as a file. This can be done using the SaveFile method. For example;

json.SaveFile('filename',format)

If the format parameter is set to true, then the file will be formatted with line breaks, and indentation (using tabs), suitable for a person to read.
If the format parameter is false (or omitted) then the file will be kept as small as possible by leaving out the formatting.

Saving a JSON Object to StringTheory

Internally the JSON is stored as a collection of objects. To use the result in a program it must be turned into a String and stored in a StringTheory object. This is done by passing a StringTheory object to the SaveString method. For example;

str  StringTheory
  code
  json.SaveString(str,format)


Once in the StringTheory object it can then be manipulated, compressed, saved or managed in any way you like.

If the format parameter is set to true, then the string will be formatted with line breaks, and indentation (using tabs), suitable for a person to read.
If the format parameter is false (or omitted) then the string will be kept as small as possible by leaving out the formatting.

Arrays

JSON supports Arrays. They look something like this

{"phone": [ "011 111 2345","011 123 4567"]}

The square brackets indicate that the field ("phone") contains a list of values, ie an array.

Clarion also supports arrays, using the DIM attribute. So creating fields like the one above is very straight-forward

PhoneGroup  Group
phone         string(20),DIM(5)
            End
  code
  json.Save(PhoneGroup)


This would result in JSON that looks like this

{
  "PHONE" : ["012 345 6789","023 456 7890"]
}

Empty items in the array, which come after the last set value, are suppressed. In the above example, only the first two array values were set, so only 2 values were included in the output. Position in the array is preserved. If phone[1] and phone[3] were set, but phone[2] was left blank then the output would read

{
  "PHONE" : ["012 345 6789","","023 456 7890"]
}

Items are considered empty if the field is a string, and contains no characters, of if the field is a number and contains a 0.

Clarion supports multi-dimensional arrays. These are written out as if they were a single dimension array.
For example;

Matrix[1,1] = 1010
Matrix[2,1] = 2010
Matrix[1,2] = 1020
Matrix[2,2] = 2020

becomes

"MATRIX" : [1010,1020,2010,2020]

Note that variables of DIM(1) are not allowed. To be an array the dimension value must be greater than 1.

It is possible (and valid) for an entire JSON file to consist of a single array.

[1,2,3,4,5]

In this case there is neither a field name, nor an array or object wrapper around the value.

dude long,dim(5)
  code
  dude[1] = 12
  dude[2] = 22
  dude[3] = 43
  json.Start()
  json.SetType(json:Null) 
! suppresses "file" boundary
  json.AddArray('',dude)  
! first parameter, fieldname, is blank.

this results in

[12,22,43]

As noted earlier, trailing empty values (0 if a number, blank if a string) are removed.

Blobs

Creating JSON files from Tables or Views that contain Blobs are supported, but unfortunately coverage can vary a bit based on the driver in use.

Tables

Creating JSON using any of the Save(Table...) , Add(Table...) or Append(Table...) methods is supported.
If the table has memo or blob fields then these are included in the export, and there is nothing specific you need to do.

If you wish to suppress MEMOs and BLOBs when saving a table set the NoMemo property to true. For example

json.start()
json.SetNomemo(True)
json.save(...)

Views

Clarion Views behave differently when using TopSpeed or SQL drivers. So if you need to save a BLOB with a VIEW then read this section carefully.

For all drivers, VIEWs can contain BLOB fields. For example;

ViewProduct View(Product)
              Project(PRO:Name)
              Project(PRO:RRP)
              Project(PRO:Description)
! This is a blob
            End

TopSpeed
The TopSpeed driver is able to detect the BLOB fields in the VIEW, and so there's no extra code for you to do.

Note 1: If the NoMemo property is set to true then the BLOB will be suppressed even if it is in the VIEW.

Note 2 : The shorthand method of projecting all fields in a table, by projecting no fields, does not include BLOB or MEMO fields. If you want to PROJECT MEMOS or BLOBs then you must PROJECT it (and all other fields) explicitly.
SQL
The SQL drivers are unable to detect BLOB fields in the VIEW structure. The BLOBS are still populated, but the explicit method to determine if the BLOB is in the VIEW or not, does not work for SQL Drivers.

jFiles adopts the following work-arounds to this issue.

a) [Default behavior]. When looping through the VIEW all the BLOB fields are checked for content. If the value is not blank then it is included in the output. In other words BLOBs with content are exported, BLOBs without content are not included. In most cases this will likely be sufficient as JSON allows fields to be excluded when they are blank.

b) If all the BLOBs from the Table are included in the VIEW then you can set the property ExportBlobsWithView to true. If this value is true then all the BLOBs will be included in each JSON record. If they are blank (or not included in the VIEW) then they will be included in the JSON as blank.

So in order to export BLOBs with VIEW records set the property ExportBlobsWithView to true.  For example;

ViewProduct View(Product)
               Project(PRO:Name)
               Project(PRO:RRP)   
              
Project(PRO:Description) ! This is a blob
            End

json.Start()
json.SetExportBlobsWithView(True)
json.Save(ViewProduct)


Note1: If the NoMemo property is set to true then the memos and blobs will not be included even if ExportBlobsWithView is set to true.

Creating Nested JSON structures from a View

jFiles 2 introduces the ability to create nested JSON structures from a VIEW declaration.  Consider this VIEW;

Invoice_view View(Invoice)
                Project(INV:ID)
                Project(INV:Date)
                Project(INV:Paid)
                Join(LIN:InvKey,INV:ID) ,INNER
                    Project(LIN:ID)
                    Project(LIN:Quantity)
                    Project(LIN:Price)
                    Join(PRO:Key,LIN:Product) ,INNER
                        Project(PRO:Name)
                    End
                End
             End


This View exports all the details for an invoice, including the invoice details, the lineitem details, and the product details.

The ideal output for a view like this is something like this;

{ "Invoices":[{
    "ID":1,
    "Date":"2019/7/1",
    "Paid":1,
    "Line Items":[{
        "ID":1001,
        "Quantity":5,
        "Price":100,
        "Product":{
            "Name":"jFiles"
        }
    }]
}]}
       


With jFiles 2 creating a nested structure like this directly from the view is possible. There are only two extra bits of code required. The example code looks like this;

json.start()
json.setTagCase(jf:caseAsIs)
json.SetNestViewRecords(true) ! [1]
json.setViewBoundary(Invoice_view,LineItems,'Line Items',jf:OneToMany)  ! [2]
json.setViewBoundary(Invoice_view,Product,'Product',jf:ManyToOne)       ! [3]
json.save(Invoice_view,str,'Invoices')


The first extra line of code is [1], this sets the NestViewRecords property and this tells jFiles that you want a nested VIEW structure.

The calls to SetViewBoundary tell jFiles a little bit more about the JOIN itself, and allows you to specify the child tag as well.

Creating Nested JSON structures from a Group or Queue

Clarion Group and Queue structures can contain complex field types. Thanks to Extended Name Attribute support it is now possible to send complex groups and queues to JSON, without having to embed code in derived methods. For example;

CustomerQueue     Queue,pre(cust)
Name                 string(100),name('Name')
Invoices             &InvoiceQueueType,name('Invoices| Queue')
                  End

json.start()
json.SetTagCase(jf:CaseAsIs)
json.Save(CustomerQueue,str)


In the above example a Queue is inside another queue - each record in the parent contains a complete queue. In the JSON output the internal queue is nested inside the parent record.

you can do the same thing with a Group, for example exporting two different tables at the same time;

backup          Group
customers         &File,name ('customers | table')
products          &File,name ('products | table')
                End

json.start()
json.SetTagCase(jf:CaseAsIs)
json.Save(backup,'backup.json')


Creating Nested JSON structures

This section follows on from the Storing Multiple Items in a JSON Object section above.

Another form of the Append method exists, which allows you to start a new collection within your collection.

Append(<Boundary>)

This starts a new collection inside an existing collection. To use this, first you need to declare a pointer to this collection;

subItem   &JSONClass

Then (after doing the CreateCollection call and so on) you can do

subItem &= Collection.Append('VersionInformation')

and after that do as many subItem.Appends as you like.

This nesting can continue to as many levels as you like.

Here is a complete example;

json        JSONClass
collection  &JSONClass
subItem     &JsonClass
  code
  json.Start()
  collection &= json.CreateCollection('Collection')
  collection.Append(Customer,'Customers')
  collection.Append(Queue:Browse:1)
  collection.Append(MemoView)
  subItem &= Collection.Append('VersionInformation')
  subItem.Append('Version','6.0.3')
  subItem.Append('Build',1234,json:numeric)
 

Formatting Field Values on Save

Up to now all the exporting of the fields has resulted in the raw data being stored in the JSON file. In some cases though it is preferable to export the data formatted in some way so that it appears in the JSON as a more portable value. For example in Clarion Dates are stored as a LONG, but if the data needs to be imported into another system then displaying the date as yyyy/mm/dd might make the transfer a lot easier.

This can be done by adding the desired Picture to the NAME attribute of the field. For example;

InvoiceQueue Queue
Date Long,name('Date | @D3')
End


All StringTheory Extended Pictures are supported. For more information on the name attribute see Extended Name Attributes.

jFiles 1

The process above is considerably simpler than the one below, and should be used wherever possible. If it is not possible, or you are reading old code (and trying to understand it) then the Files 1 method is described below.

This is done by embedding in the FormatValue method in your json class. The method is declared as;

json.FormatValue PROCEDURE (String pName, String pValue, *LONG pLiteralType),String

Note the LiteralType parameter. If you are changing the type of the data (for example, changing the DATE from a Numeric to a String) then you need to change the LiteralType value as well. The value of this parameter should be one of

json:String   EQUATE(1)
json:Numeric  EQUATE(2)
json:Object   EQUATE(3)
json:Array    EQUATE(4)
json:Boolean  EQUATE(5)
json:Null     EQUATE(6)

As the name of the field is passed into the method, it is straight-forward to create a simple CASE statement formatting the fields as required. This code is embedded before the parent call. Also note that the value in pName is the JSON field name - not the Clarion field name. And this value is case sensitive.

case pName
of 'DATUM'
  pLiteralType = json:string
  Return clip(left(format(pValue,'@d1')))
of 'TIME'
  pLiteralType = json:string
  Return clip(left(format(pValue,'@t4')))
end


In the above case the Datum and Time fields are formatted, all other fields are left alone and placed in the file "as is".

Renaming Fields on Save

When exporting JSON from a structure the External Name of each field is used as the "Tag" : name in the JSON. For example

xQueue       Queue
field1         string(255),Name('Total')
             End


results in JSON like this;

{"Total" : "whatever"}

Ideally the external Name attribute of the field contains the correct value for the tag.
There are times however when you need to override this. You can do this by adding an attribute to the NAME attribute. The RENAME and JSONNAME attributes are supported. For example;

xQueue       Queue
field1         string(255),Name('Total | JsonName(Extra Total)')
field2         string(255),Name('SubTotal | Rename(Sub Total)')
             End


For more information on the name attribute see Extended Name Attributes.

jFiles 1

The process above is considerably simpler than the one below, and should be used wherever possible. If it is not possible, or you are reading old code (and trying to understand it) then the jFiles 1 method is described below.

In jFiles 1 this is done by embedding code into the AdjustFieldName method, AFTER (or BEFORE) the PARENT call.

Example

json.AdjustFieldName PROCEDURE (StringTheory pName, Long pTagCase)
  CODE
  PARENT.AdjustFieldName (pName,pTagCase)
  case pName.GetValue()
  of 'Total'
    pName.SetValue('Totalizer')
  End


Note that the field name in the above CASE statement is a case-sensitive match. If you need a case insensitive match then UPPER or LOWER both the CASE and OF values.

The Parent call performs the replaceColons and remove prefix work. So, if you put the CASE before the parent call, then the field will come in "Clarion Style", if after the parent call then "JSON Style". It is up to you which side of the Parent call you put your code onto.

Saving Nested Structures - another approach

Consider the following JSON;

{  "customer" : {
        "Name" : "Bruce",
        "Phone" : "1234 567 89",
        "Invoices" : [
            {
                "InvoiceNumber" : 1,
                "LineItems" : [
                    {
                        "Product" : "iDash",
                        "Amount" : 186.66
                    }
                ]
            },
            {
                "InvoiceNumber" : 2,
                "LineItems" : [
                    {
                        "Product" : "Runscreen",
                        "Amount" : 179.75
                    }
                ]
            }
        ]
    }
}

This is constructed from a Group (Customer information) which contains a Queue (of Invoices) and each invoice contains a Queue of Line Numbers. It's worth pointing out that the line items queue is a simple JSON form of a queue, and the Invoice Queue is again just a JSON form of a queue.

The Clarion structures for the above are as follows;

CustomerGroup   Group
Name              string(50),name('Name')
Phone             string(50),name('Phone')
                End

InvoicesQueue   Queue
InvoiceNumber     Long,name('InvoiceNumber')
                End

LineItemsQueue  Queue
Product           String(50),name('Product')
Amount            Decimal(8,2),name('Amount')
                 End

In this case the structures are a Group and Queues, but you could also use Views or Tables if you wanted to.

In order to achieve the result three jFiles objects are used;

CustomersJson Class(JSONClass)
AssignValue     PROCEDURE (JSONClass pJson,StringTheory pName,|
                            *Group pGroup,*Long pIndex,Long pColumnOffset),VIRTUAL
              End
! CustomersJson
InvoicesJson  Class(JSONClass)
AssignValue     PROCEDURE (JSONClass pJson,StringTheory pName, |
                           *Group pGroup,*Long pIndex,Long pColumnOffset),VIRTUAL
              End
! InvoicesJson
LineItemsJson JSONClass

As you can see two of the classes (the ones that have children) will have some override code in the AssignValue method. (More on that in a moment.)

For the purposes of this example, the code for populating the structures is omitted.

The basic code to generate the JSON file looks like this;

! Assume the CustomerGroup is primed at this point
CustomersJson.Start()
CustomersJson.SetTagCase(jF:CaseAsIs)
CustomersJson.Save(CustomerGroup,'customer.json','customer',true)


In order to include the InvoicesQueue inside the group some code is added to the CustomersJson.AssignValue method. The code looks like this;

CustomersJson.AssignValue PROCEDURE (JSONClass pJson,StringTheory pName,|
                                     *Group pGroup,*Long pIndex, Long pColumnOffset)
  Code
  PARENT.AssignValue (pJson,pName,pGroup,pIndex,pColumnOffset)
  If pName.GetValue() = 'Phone'
    do PrimeInvoicesQueue
! Prime the Queue with the values for this customer
    InvoicesJson.Start()
    InvoicesJson.SetTagCase(jF:CaseAsIs)
    InvoicesJson.Save(InvoicesQueue, ,'Invoices')
! Save the Queue to a Json Object
    pJson.AddCopy(InvoicesJson, ,true)
! Add (a copy of) the InvoicesJson object into CustomersJson
  End


There are a few interesting things to note in the above code.

a) Notice it's checking for the JSON tag 'Phone' as it appears in the JSON file. This is simply the position in which the Invoice queue will be injected. As it is after the parent call, it will come after the Phone field in the JSON file. If it was before the parent call it would come before the Phone field. If it came before the parent call, and the parent was not called at all, then the Phone field would be excluded from the JSON.

b) The code to Prime the Queue, and Save that Queue to the InvoicesJson object is standard code as described earlier in this document. Notice the omitted parameter in the call to .Save.

cc) The AddCopy call is where the magic happens. This adds a copy of the InvoicesJson object into the CustomersJson object at the position specified by the passed in parameter pJson.

d) The parameters pGroup, pIndex and pColumnOffset are not useful in your embed code, they are used in the call to the parent method.

As this example covers three layers of JSON, the technique is repeated for the InvoicesJson object. It too has an AssignValue method, and it uses similar code to inject the LineItems at that point;

InvoicesJson.AssignValue PROCEDURE (JSONClass pJson,StringTheory pName,|
                                   *Group pGroup,*Long pIndex,Long pColumnOffset)
  CODE
  PARENT.AssignValue (pJson,pName,pGroup,pIndex,pColumnOffset)
  if pName.GetValue() = 'InvoiceNumber'
    do PrimeLineItemsQueue
    LineItemsJson.Start()
    LineItemsJson.SetTagCase(jF:CaseAsIs)
    LineItemsJson.Save(LineItemsQueue, ,'LineItems')
    pJson.AddCopy(LineItemsJson)
  End




Saving Nested Structures - yet another approach

Consider the following JSON;

[
	{
		"_id" : "7123098",
		"accountName" : "Charlies Plumbing",
		"mainContact" : "",
		"mainPhone" : "",
		"accountLogins" : [
			{
				"loginName" : "Administrator",
				"loginPwd" : "secret",
				"loginHistory" : [
					{
						"loginDate" : "2017/07/17",
						"loginTime" : "16:27"
					},
					{
						"loginDate" : "2017/07/18",
						"loginTime" : "15:26"
					}
				]
			},
			{
				"loginName" : "Operator",
				"loginPwd" : "1234",
				"loginHistory" : [
					{
						"loginDate" : "2017/07/17",
						"loginTime" : " 8:15"
					},
					{
						"loginDate" : "2017/07/18",
						"loginTime" : "15:51"
					}
				]
			}
		],
		"accountContacts" : [
			{
				"contactName" : "Beatrice",
				"contactPosition" : "CEO"
			},
			{
				"contactName" : "Timothy",
				"contactPosition" : "Sales"
			}
		]
	}
]

This is a highly nested structure. It is an AccountsQueue, which in turn contains a Logins Queue and a Contacts Queue. The Logins Queue contains a Login History queue.

Here is the Accounts queue declaration;

AccountsQueue   Queue
id                string(20),name('_id')
accountName       string(255),name('accountName')
mainContact       string(255),name('mainContact')
mainPhone         string(255),name('mainPhone')
accountLogins     &accountLoginsQueue,name('accountLogins')
accountContacts   &accountContactsQueue,name('accountContacts')
                End


The Contacts queue declaration

accountContactsQueue Queue,type
contactName             string(100),name('contactName')
contactPosition         string(100),name('contactPosition')
                     End


The Logins queue

accountLoginsQueue Queue,type
loginName             string(100),name('loginName')
loginPwd              string(100),name('loginPwd')
loginHistory          &loginHistoryQueue,name('loginHistory')
                   End


and finally the History queue

loginHistoryQueue Queue,type
loginDate           string(10),name('loginDate')
loginTime           string(10),name('loginTime')
                  End


Populating nested queues has to be done carefully. The queue pointers are allocated using the NEW statement whenever a record is created. Here is a single record added to the Accounts queue (with various child queue entries added as well.)

clear(AccountsQueue)
AccountsQueue.accountLogins &= new(accountLoginsQueue)
! this happens for each new AccountsQueue record.
AccountsQueue.accountContacts &= new(accountContactsQueue)

! set some values
AccountsQueue.id = '7123098'
AccountsQueue.AccountName = 'Charlies Plumbing'

! add some accounts
AccountsQueue.accountLogins.loginName = 'Administrator'
AccountsQueue.accountLogins.loginPwd = 'secret'
AccountsQueue.accountLogins.loginHistory &= new loginHistoryQueue
AccountsQueue.accountLogins.loginHistory.loginDate = format(today()-1,@d10)
AccountsQueue.accountLogins.loginHistory.loginTime = format(random(360000*8, 360000*18),@t1)
add(AccountsQueue.accountLogins.loginHistory)

AccountsQueue.accountLogins.loginHistory.loginDate = format(today(),@d10)
AccountsQueue.accountLogins.loginHistory.loginTime = format(random(360000*8, 360000*18),@t1)
add(AccountsQueue.accountLogins.loginHistory)

add(AccountsQueue.accountLogins)

AccountsQueue.accountLogins.loginName = 'Operator'
AccountsQueue.accountLogins.loginPwd = '1234'
AccountsQueue.accountLogins.loginHistory &= new loginHistoryQueue

AccountsQueue.accountLogins.loginHistory.loginDate = format(today()-1,@d10)
AccountsQueue.accountLogins.loginHistory.loginTime = format(random(360000*8, 360000*18),@t1)
add(AccountsQueue.accountLogins.loginHistory)

AccountsQueue.accountLogins.loginHistory.loginDate = format(today(),@d10)
AccountsQueue.accountLogins.loginHistory.loginTime = format(random(360000*8, 360000*18),@t1)
add(AccountsQueue.accountLogins.loginHistory)

add(AccountsQueue.accountLogins)

!add some contacts
AccountsQueue.accountContacts.contactName = 'Beatrice'
AccountsQueue.accountContacts.contactPosition = 'CEO'
add(AccountsQueue.accountContacts)
AccountsQueue.accountContacts.contactName = 'Timothy'
AccountsQueue.accountContacts.contactPosition = 'Sales'
add(AccountsQueue.accountContacts)

Add(AccountsQueue) ! save the queue record.


Sending even a complex structure like to this to JSON is relatively easy.

First the JSON object is declared. You can do this in code, or let the extension template declare it for you. Notice the AddByReference method, that will be fleshed out in a moment.

json             Class(JSONClass)
AddByReference     PROCEDURE (StringTheory pName,JSONClass pJson),VIRTUAL
                 End


Secondly the json object is called as normal;

json.Start()
json.SetTagCase(jF:CaseAsIs)
json.SetColumnType('accountLogins',jf:Reference)
! these tag names are case sensitive.
json.SetColumnType('loginHistory',jf:Reference)
json.SetColumnType('accountContacts',jf:Reference)
json.Save(AccountsQueue,'json.txt')

Notice the extra calls to SetColumnType. These tell the class that these fields are reference values, and so need to be saved separately.

The final step is to flesh out the AddByReference method. When the class encounters one of these reference fields it calls the AddByReference method. The code in there looks something like this;

json.AddByReference PROCEDURE (StringTheory pName,JSONClass pJson)
  CODE
  case pName.GetValue()
  of 'accountLogins'
    pJson.Add(AccountsQueue.accountLogins)
  of 'accountContacts'
    pJson.Add(AccountsQueue.accountContacts)
  of 'loginHistory'
    pJson.Add(AccountsQueue.accountLogins.loginHistory)
  end
  PARENT.AddByReference (pName,pJson)

Remember the tag names are case sensitive so be careful entering them here.

Disposing Nested Queues

This section has nothing to do with jFiles, but since the above example shows how to load a nested Queue structure, it's probably worth covering the Disposal of nested queue structures here. If disposal is not done correctly then a memory leak will occur.

The key lines to worry about in the above code are;

AccountsQueue.accountLogins &= new(accountLoginsQueue) ! this happens for each new AccountsQueue record.
AccountsQueue.accountContacts &= new(accountContactsQueue)

and

AccountsQueue.accountLogins.loginHistory &= new loginHistoryQueue

These lines are creating queues on the fly, and each call to new MUST have a matching call to dispose. When deleting a row from AccountsQueue or AccountsQueue.AccountLogins (and that includes deleting all rows) the child queues themselves must first be disposed. It's important to manually do this before the procedure ends - it will not be done automatically.

the basic idea is to loop through the queue, deleting the sub queues as you go.

For example;

Loop While Records(AccountsQueue)
  Get(AccountsQueue,1)
  Loop while records(AccountsQueue.accountLogins)
    Get(AccountsQueue.accountLogins,1)
    Free(AccountsQueue.accountLogins.loginHistory)
    Dispose(AccountsQueue.accountLogins.loginHistory)
    Delete(AccountsQueue.accountLogins)
  End
  Free(AccountsQueue.accountLogins)
  Dispose(AccountsQueue.accountLogins)
  Free(AccountsQueue.accountContacts)
  Dispose(AccountsQueue.accountContacts)
  Delete(AccountsQueue)
End

Field Matching Tips

When creating a JSON file, or loading a JSON file into a structure, it is necessary to match the field names in the JSON file with the field names in your structure. There are properties which assist in making a good match.

These properties can be set to default values via the global extension, the local extension, or can be set in embed code before the object is used. These properties are not reset by a call to json.Start().

Note that all the properties should be set using their SET method, and retrieved using their GET method. For example setting the RemovePrefix property is done using the SetRemovePrefix(whatever) method. And it can be retrieved using the GetRemovePrefix() method.

RemovePrefix

Clarion structures allow for the use of prefixes, which then form part of the field name. If this property is set when you create JSON then the prefix (and colon) are omitted from the JSON and only the "name" part of the fieldname is used.

IF you are importing JSON, and the JSON was created by another entity, then it's likely the fields in the JSON are not prefixed. In that case you should set this property to true as well, so that the matcher matches on names-without-prefixes.

PrefixChars

In Clarion a colon (:) character is used to separate the prefix from the field name. Incoming JSON may be using an alternate character (often an underscore(_) ) to separate the prefix from the rest of the name.

To complicate the issue colons, and underscores, are valid characters in Clarion field names, table names, and prefixes. If you do have colons or underscores in the name then that brings MaxPrefixLengthInJSON into play.

MaxPrefixLengthInJSON

To make identifying a prefix easier, it can be helpful to tell jFiles about the length of any expected prefix. So if the length of all your prefixes are say 3 characters, then you should set this value to 4. (3 for the prefix, plus one for the separator.) Any separators in the string AFTER this length will not be treated as a prefix separator.

ReplaceColons

Colons are a legal character in Clarion field names. However in most languages they are not. Therefore to create JSON which is portable into other systems it may be necessary to replace any colons with some other character (or characters) - most usually an underscore character. If you are including the prefix in the name then this setting becomes doubly important.  The default value of this property is true.

ReplacementChars

The default replacement character for a colon is an underscore character. However if you wish to replace it with some other combination of characters then you can set this property to whatever you like, up to 10 characters long.

TagCase

JSON is explicitly case sensitive. When creating JSON you can control the case using the TagCase property. Valid values for this property are;

jf:CaseUpper
jf:CaseLower
jf:CaseAsIs
jf:CaseAny


As the equates suggest, CaseUpper forces all the tags to be uppercase, CaseLower forces all the tags to be lower case, and CaseAsIs uses the case as set in the fields External Name. (If there is no External Name for a field then Upper case is used.)
CaseAny is only used on a Load. It matches incoming node names to local field names regardless of case.

Labels vs Names

In Clarion fields (fields in a table, queue or group, or just variables by themselves) have a label, which is the identifier in column 1 of the source code.
This is not the name of the field (although they are often called "Field Names". The Name of a field only exists if you have set the ,Name property for the field. Since Clarion is a case insensitive language all labels are seen as UPPER case by the compiler.

 If you are unclear on this please see ClarionHub.

So when importing make sure you understand this point, especially when setting the TagCase property as mentioned above.

Embedding

The JSON object can be thought of as a tree. The root JSON object contains other JSON objects, and those ones contain other ones and so on. This is a very elegant approach to the code, but it does have one drawback - code embedded in the methods of the root object (ie the object in your procedure) does not get called when a method on one of the child objects is called.

This means that embedding code in most of the methods will not be useful because it will not execute when you expect it to. However some methods will execute and are suitable for adding embed code. They are;

AddByReference
AddQueueRecord
AdjustFieldName
AssignField
AssignMissingField
AssignValue
DeformatValue
ErrorTrap
FormatValue
InsertFileRecord
SetColumnType
Trace
UpdatefileRecord
ValidateField
ValidateRecord

In addition many methods are called only by your program so are suitable for embedding. They are

Start
CreateCollection
Save  (any form), SaveFile, SaveString
Load  (any form), LoadFile, LoadString
Append (any form)


Templates

Global Template

General Tab

Disable All jFiles Features
This disables the jFiles template, and no template code will be generated into the application. This is useful for debugging.

Options Tab

Remove Prefix
This is a default setting for all the local objects. (It can be overridden at the local level.) If this is on then the prefix, and colon, will be removed from all the field names when creating JSON files.
Replace Colons
This is a default setting for all the local objects. (It can be overridden at the local level.) In Clarion the colon character is valid in a fieldname, but in most other languages is it not. This option allows you to replace the colon character wherever it appears in the fieldname with an alternate character (by default an _) on export. Likewise on import incoming _ characters are mapped to colons.

Multi-DLL Tab

This is part of a Multi-DLL Program
Tick this on if this application is a DLL or EXE which is part of a multi-app suite of apps.
Export jFiles Classes from this DLL
Tick this on only if this is the data DLL in the suite of apps.

Classes Tab

Class Version
An internal version number.

Local Extension Template

General Tab

Do Not generate this object
If this is on the the object will not be generated in this procedure.

Options Tab

Remove Prefix
If this is on then the prefix, and colon, will be removed from all the field names when creating JSON files.
Replace Colons
In Clarion the colon character is valid in a fieldname, but in most other languages is it not. This option allows you to replace the colon character wherever it appears in the fieldname with an alternate character (by default an _) on export. Likewise on import incoming _ characters are mapped to colons.
Replacement Char
The character to use as a replacement for colons. By default an underscore ( _ ) is used.

Using in a Multi-DLL System

In your Data-DLL: In your other applications (that use jFiles) - including DLL and EXE apps:

Example

There are a number of examples that ship with jFiles;
  1. ABC
    This is the most full-featured example which shows off the most uses.
  2. Legacy
    This is similar to the ABC example, but uses the Clarion template chain.
  3. Multi-DLL ABC
    This is a simple, ABC based, Multi-app system. The jFiles extension has been added to all the apps - take special note of the settings in each app.
  4. Multi-DLL Legacy
    This is a simple, Clairion template based, Multi-app system. The jFiles extension has been added to all the apps - take special note of the settings in each app.

Class Reference

 

JSONClass

Properties

Note that all the properties should be set using their SET method, and retrieved using their GET method. For example setting the RemovePrefix property is done using the SetRemovePrefix(whatever) method. And it can be retrieved using the GetRemovePrefix() method.
 
Property Type Description
Action LONG Useful in the ValidateRecord method, this property is set to one of jf:Save or jf:Load
ColumnDisabled LONG Allows a column in the structure to be suppressed when doing a Save. See also the DisableField method to suppress the field using the name rather than the number.
DemoMode LONG If set to 0 then Demo Mode is off. If set to > 0 then demo mode is on. If demo mode is on then the calls to Save and Append methods will not read the actual structure. Rather a sample json file will be created, with all the values set to the contents of the DemoModeValue property. For multiple record structures (Table, View, Queue) the value in DemoMode will determine the number of records generated into the list. For example, DemoMode=2 means 2 sample records are generated into the list.
DemoModeValue CSTRING(20) The string to use in place of a value when the object is in demo mode. For example;
json.DemoModeValue = '**value**'
DisableOveredFields LONG Only applies when Saving a Table to a Json object. If set then all fields in the table which are "overed" by another field will not be included in the Json output.
DisableOverFields LONG Only applies when Saving a Table to a Json object. If set then all fields in the table which are "over" another field will not be included in the Json output.
DontSaveBlanks BYTE If this is true, then equivalent to setting DontSaveBlankArrayValues and DontSaveBlankNumbers and DontSaveBlankStrings to true.
DontSaveBlankArrayValues BYTE If this is true, then Clarion Arrays are saved with with empty values omitted. for example;
s  Long,dim(4)
s[1]  = 1
s[4] = 4

would become
[1,4]
DontSaveBlankNumbers BYTE If this is true then numeric fields, which are 0 are not written out to the JSON file.
DontSaveBlankStrings BYTE If this is true then string fields, which are blank are not written out to the JSON file.
Error String(256) A string containing the description of the most recent error.
F FILE A pointer to the Table which is being saved from, or imported into.
FreeQueueBeforeLoad LONG If true (default) then the Queue is FREEd before loading from JSON when calling the Load method.
G GROUP A pointer to the Group which is being saved from, or imported into.
JsonDecode LONG If true (the default) then incoming strings are automatically json-decoded during LOADing. If false then the strings are left as-is. Use SetJsonDecode and GetJsonDecode methods to access this property.
JsonEncode LONG If true (the default) then outgoing strings are automatically json-encoded during SAVEing. If false then the strings are left as-is. Use SetJsonEncode and GetJsonEncode methods to access this property.
MatchByNumber LONG If true, then on a Load fields are matched to the structure based on position, not name. This is useful for JSON which contains just values, no names.
NestViewRecords LONG If this is set to true, and a VIEW is being Saved, then the output will be a nested JSON output, which nests in the same way that the tables are JOINed. See Creating Nested JSON structures from a View and SetViewBoundary for more.
NullNumericValue REAL If a field in the incoming json is set as null, and the field type is a number, then this value will be used in the structure. The default for this property is 0.
NullStringValue STRING(50) If a field in the incoming json is set as null, and the field type is a string, then this value will be used in the structure. The default for this property is a blank string.
OmittedStringDefaultValue STRING(50) If a field is omitted from the incoming JSON, and the SetOmittedString property is true, then the string field in the structure is set to this value when doing a LOAD.
OmittedNumericDefaultValue REAL If a field is omitted from the incoming JSON, and the SetOmittedNumeric property is true, then the number field in the structure is set to this value when doing a LOAD.
Q QUEUE A pointer to the Queue which is being saved from or imported into.
RecordsInserted LONG The number of records inserted into the Table or Queue in the most recent Load
RecordsUpdated LONG The number of records updated in the Table in the most recent Load
RemovePrefix LONG Set to true or false, this determines if the prefix is removed from the local field before writing it to json, or finding a match in the incoming json text.
ReplaceColons LONG Set to true or false, if set to true any colons in the Clarion field name are replaced with the character(s) in the ReplacementChars property.
ReplacementChars STRING(10) The characters to use to replace colons with. The default value is an underscore ( _ ).
SaveBlankStringsAsNull LONG If this is set to true, and the string being saved is blank, then the value will be saved as null instead. The default value for this property is false.
SaveFields LONG The number of fields in the structure to save. If set to 0 then all the fields in the structure are saved.
SaveNullStringsAsNull LONG If this is set as true (the default) AND NullStringValue is set to something AND the string being saved matches this value, then the value will be set to null in the JSON rather than the string value.
SaveRecords LONG The number of records in the structure to save. If set to 0 then all the records are saved. Can be useful when exporting the "top n records" in any result set. See also the SkipRecords property.
WriteOmittedNumeric LONG If set to true, and a numeric field in the structure is omitted from the incoming JSON (on a load) the field in the structure will be set to the self.OmittedNumericDefaultValue property. The default for this property is false. If set to false the field in the structure is set to 0.
WriteOmittedString LONG If set to true, and a string field in the structure is omitted from the incoming JSON (on a load) the field in the structure will be set to the self.OmittedStringDefaultValue property. The default for this property is false. If set to false the field in the structure is set to a blank string.
SkipRecords LONG This property works in conjunction with the SaveRecords property. If this is set then the first n rows are skipped, before rows are added to the output. For example if SkipRecords is set to 10, and SaveRecords is set to 5, then records 11 through 15 will be added to the JSON.
TagCase LONG Set to one of jf:CaseUpper (the default,) jf:CaseLower, jf:CaseAsIs or jf:CaseAny. JSON is case sensitive, so setting the case in created JSON , and matching the case in imported JSON is very important. Note that this property does not alter "Boundary" parameters as passed to many of the functions - when setting a boundary you should set the case at the same time.
When changing the TagCase for an object that has already been loaded with data, it is best to use the SetTagCase method instead of setting the property directly.
UpdateFileOnLoad LONG Set to true or false, if set to true then "duplicate" records in a Load are updated with (possibly) new values. If set to false then duplicate records are not imported and are discarded. Note that this feature requires that the Table being loaded has a Primary Key declared. The Primary Key fields are used to identify the record, and hence identify duplicates. Default value is true.
Using LONG Set to one of jq:Queue, jf:Table or jf:View. This identifies which of self.F, self.Q or self.V property has been set.
V VIEW A pointer to the View which is being saved from.

Methods

JSONClass

Add

Add (String pName, String pValue, Long pType = json:String)
Add (String pName)
Add (JSONClass pJson, Long pDispose=False)
Add (*GROUP pGroup, Long pColumnOffset=0)
Add (QUEUE pQueue, Long pLoop=true)
Add (FILE pTable, Long pLoop=true)
Add (VIEW pView, Long pLoop=true)

Description

This method adds a new node to the current object.

Parameters

Parameter Description
pName The name of the node. This can be a blank string if the node does not have a name.
pValue The value of the node. This can be left blank if the node has no current value, or if the node is an array or object. If this parameter is omitted then the pType parameter also has to be omitted.
pType The type of the value. The default for this is json:string. For actual values it could be one of json:string, json:numeric, json:boolean, json:null, json:FromString or json:auto.

If set to json:auto then the value is inspected and the type is set based on the value.
If set to json:FromString then the pValue parameter is first parsed as JSON and then added to the object. (See AddFromString)

If this value is json:Object then an object is created, if the value is json:Array then an array (a list) is created.
pJson A pointer to another JSON object, or node, to add into this object. Note that object is not copied, a pointer is inserted. So the other object needs to remain in scope for as long as this object remains in scope. To copy a JSON node from another object use the AddCopy method instead.
pDispose If this is set to false then this node is not disposed when the current object is disposed. It is assumed that the other object will dispose itself as necessary. If this is set to true then the node will be disposed by this object when it goes out of scope. Only do this if nothing else is pointing to this node.
pGroup Adds the contents of a group to the current JSON object. This does not create a group object in the output. To create an object use the Append method.
pQueue Adds the contents of a queue structure (possibly repeated for each record) to the current JSON object. This does not create a new list structure, see the Append method to do that.
pTable Adds the contents of a table structure (possibly repeated for each record) to the current JSON object. This does not create a new list structure, see the Append method to do that.
pView Adds the contents of a view structure (possibly repeated for each record) to the current JSON object. This does not create a new list structure, see the Append method to do that.
pColumnOffset Used when adding a group, inside another group, and DisableField has been used.
pLoop Used when adding a Queue, File or View. If set to true (the default value) the jFiles will loop through the structure adding every record. If set to false then only the current record buffer is saved, as a json:object, not as a json:array.

Example

Json    JSONClass
Product &JSONClass
  code
  json.start()
  json.add('company','capesoft')
  json.add('location','cape town')
  Product &= json.add('product','', json:Array)
  Product.add('name','jfiles')

Return Value

The method returns a pointer to the new node that has been added.

See Also

Constructing JSON manually, AddArray, AddCopy, Append, AddFromString

JSONClass

AddArray

AddArray (String pName)
AddArray (String pName, *Long[] pValue)
AddArray (String pName, *String[] pValue)

Description

Add an Array to the current object. A Clarion array of LONG's or STRING's can be passed into the method, or the array can be added empty.

Parameters

Parameter Description
pName The name of the array. This can be left blank if desired.
pValue A Clarion array of LONG or STRING values. (Other data types are not supported.)

Example

Json    JSONClass
names   string(20),dim(10)
  code
  json.start()
  json.add('company','capesoft')
  json.add('location','cape town')
  json.addArray('product',names)

Return Value

The method returns a pointer to the new node that has been added.

See Also

Add, AppendArray, Arrays, AppendArray

JSONClass

AddByReference

AddByReference(StringTheory pName, JSONClass pJson)

Description

This method is called when a reference field is encountered in a structure. The reference field must be previously identified  with a call to SetColumnType.

Parameters

Parameter Description
pName The name of the field. This is the JSON name (as it will appear in the output) and is case sensitive.
pJson The JSON class where the value of the field must go.

Notes

This method provides an embed point in the derived object which allows the pointer to be resolved, and the appropriate Add method to be called.

Example

json.AddByReference PROCEDURE (StringTheory pName,JSONClass pJson)
  CODE
  case pName.GetValue()
  of 'accountLogins'
    pJson.Add(AccountsQueue.accountLogins)
  of 'accountContacts'
    pJson.Add(AccountsQueue.accountContacts)
  of 'loginHistory'
    pJson.Add(AccountsQueue.accountLogins.loginHistory)
  end

Return Value

The method returns nothing

See Also

Saving Nested Structures - yet another approach , SetColumnType

JSONClass

AddCopy

AddCopy (JSONClass pJson, <String pBoundary>, Long pInside=false)

Description

Makes a clone of the pJSON object and adds it to the current object. This allows this document to be complete, without referencing a node in another document.

Parameters

Parameter Description
pJson A JSON class object
pBoundary The boundary name for the new node. This can be left blank if desired.
pInside By default the JSON being copied is injected into the current object "as is". However if this JSON is a single-node, tree, without a boundary, and you are not supplying a boundary, then you can clone the first node of the pJson object into your JSON. In other words, one layer of "wrapping" is removed.

Example

JSONClass.Merge procedure(JSONClass pJson)
x long
y long
JsonX &JsonClass
JsonY &JsonClass
node  &JsonClass
  code
  loop x = 1 to pJson.records()
    jsonx &= pJson.Get(x)
    node &= self.GetByName(jsonx.ObjectName,1)
    if node &= null
      self.AddCopy(jsonx,jsonx.ObjectName)
    else
      loop y = 1 to jsonx.records()
        jsony &= jsonx.get(y)
        node.AddCopy(jsony)
      end
    end
  end

Return Value

The method returns a pointer to the new JSON node that has been created.

See Also

Add
JSONClass

AddFromString

AddFromString(String  pName, String pValue)
AddFromString(String  pName, StringTheory pValue)

Description

This method is called when you have a string, which contains text that is valid JSON. This JSON is parsed, and added to the current object. It is the same as calling the regular ADD method with the Type parameter set to json:FromString.

Parameters

Parameter Description
pName The name of the node. This can be a blank string if the node does not have a name.
pValue The value of the node. This is a string containing valid JSON

Example

str  StringTheory
json  JsonClass
  code
  str.SetValue('{{"name": "capesoft","phone": "087 828 0123"}')
  json.addFromString('customer',str)


Return Value

The method returns a pointer to the new node that has been added.

See Also

Add

JSONClass

AddQueueRecord

AddQueueRecord(Long First)

Description

This method is called when a record is being added to a queue when doing a Load. This method does the ADD to add the record to the queue.

Parameters

Parameter Description
First (optional) If omitted the parameter is false. If set to true then the record is added to the front of the queue.

Notes

You would not usually call this method directly. However if you wish to do additional work just before, or just after a record is added, then this is a useful method to embed code into.

Return Value

The method returns jf:Ok if the ADD is successful, and the RecordsInserted property is incremented. If the ADD fails then the ErrorTrap method is called, and this method returns jf:ERROR.

See Also

InsertFileRecord, UpdateFileRecord

JSONClass

AdjustFieldName

AdjustFieldName(StringTheory Name)

Description

Converts a field name into a JSON label by applying the RemovePrefix, ReplaceColons and TagCase properties.

Parameters

Parameter Description
Name The field name to apply the necessary changes to.

Notes

You would not usually call this method directly. However if you wish to do additional work just before, or just after a record is added, then this is a useful method to embed code into.

Return Value

Nothing

JSONClass

Append

Append (Table | Queue | View | Group , <String Boundary>, Long pOptions=0)
Append (String pName, string pValue, Long pType)
Append (JSONClass pJson)
Append (<String pBoundary>)

Description

Used to append a Clarion structure to a collection or JSON object.

Parameters

Parameter Description
Table / Queue / View / Group The structure to append to the collection.
Boundary The boundary to use in the collection to identify this structure. If omitted or blank the a default boundary will be used.
pOptions 0 by default.
Can be json:Noboundary which specifically adds the node without a boundary name.
Can be json:array. Only available if the first parameter is a GROUP.
If this is set then this group is added to the JSON as a single-value List (ie an array).
pArray  
pName The name of an item to add to the JSON
pValue The value of the item to add to the JSON
pType The Type of the item being added to the JSON. this should be one of;
json:String, json:Numeric, json:Booleanjson:Null, json:Auto, json:Json
If Json:Auto is selected then the node will attempt to determine the correct type based on the contents of pValue.
The Json:Json type is a string which has already been JSON encoded, or is itself valid JSON.
pJson An existing JSONClass is added into this JSON class.
pBoundary An empty JSON node

Return Value

The method returns a JSONClass object, which represents the json of this structure.

See Also

AppendArray, Save, Add

JSONClass

AppendArray

AppendArray( String pName, *String[] pValue | *Long[] pValue)

Description

Used to append an array, as a single field, to the Json.

Parameters

Parameter Description
pName The name of the field to add to the Json.
pValue The Array (of Strings, or Longs) to be added to the JSON.

Return Value

The method returns a JSONClass object, which represents the json of this structure.

Example

tots String(20),dim(5)
  Code
  json.start()
  collection &= json.CreateCollection('Collection')
  collection.appendArray('Totals',tots)


creates JSON that looks like this;

{
    "Collection" : {
        "Totals" : [1000,2000,3000,"",""]
    }
}


See Also

Append

JSONClass

AssignMissingField

AssignMissingField (*Cstring pColumnName)

Description

This is called during a Load(Table/Queue/Group) where a field in the Table/Queue/Group is not found in the JSON. This allows you to embed your own assignment code into this routine, where you need to set values other than the OmittedStringDefaultValue and OmittedNumericDefaultValue properties.

This method is called after the default values have been assigned, so the field will (possibly) be primed with the default value when the method is called.

Parameters

Parameter Description
pColumnName the name of the field, in the structure, which was not found in the JSON data. This parameter is passed by reference for performance reasons, changing the pColumnName parameter has no effect.

Return Value

The method returns nothing.

Example

json                 class(jsonclass)
AssignMissingField     PROCEDURE(*Cstring pColumnName),Derived
                     end
  Code
  json.start()
  json.SetTagCase(jf:Caselower)
  json.load(invoices,'invoices.json')

json.AssignMissingField     PROCEDURE(*Cstring pColumnName)
  code
  if lower(pColumnName) = 'date'
    inv:date = today()
  end
   


creates JSON that looks like this;

{
    "Collection" : {
        "Totals" : [1000,2000,3000,"",""]
    }
}


See Also

Append

Copy

Copy(JSONClass pJson)

Description

Makes the current object a copy of the passed in object. the value currently in the class is discarded. Then all the nodes in the pJson object are duplicated as new nodes in the current object. The passed-in pJson object is unchanged by this call.

Parameters

Parameter Description
pJson The source JSON object.

Return Value

Nothing. The current object is changed to match the source object.

See Also

AddCopy, AddByReference

JSONClass

DeformatValue

DeformatValue(String Name, String Value)

Description

This method is called when the value of a JSON node is inspected (typically before placing in a Clarion field.) The method allows you to embed your own code so that you can transform the JSON value into something more appropriate to your data structure.

Parameters

Parameter Description
Name The name of the value being deformatted.
Value The raw value before deformatting.

Return Value

The method the deformatted value.

See Also

DeformattingJSON, FormatValue

JSONClass

Delete

Delete(String pPropertyName)

Description

This method has been deprecated, and removed from the library. Use DeleteByName instead.

JSONClass

DeleteByName

DeleteByName(String pPropertyName)

Description

This method deletes a node from the JSON tree. Only a node at the current level (level 1 of this node) can be deleted.

Parameters

Parameter Description
pPropertyName The name of the value being deleted.
Example

json  jsonClass
jsonnode  &jsonClass
  code
  json.LoadString(SomeJsonInStringTheoryObject)
  jsonNode &= json.GetByName('Customer')
  if not jsonNode &= Null
    jsonNode.DeleteByName('Address')
  end

Return Value

Returns jf:ok if a node was deleted. Returns jf:Error if the name passed did not match an existing child.

See Also

DeformattingJSON, FormatValue

JSONClass

DeleteByNumber

DeleteByNumber(long pIndex)

Description

This method deletes a node from the JSON tree. Only a node at the current level (level 1 of this node) can be deleted.

Parameters

Parameter Description
pIndex The index number (in the current class) to be deleted.
Example

json  jsonClass
jsonnode  &jsonClass
  code
  json.LoadString(SomeJsonInStringTheoryObject)
  jsonNode &= json.GetByName('Customer')
  if not jsonNode &= Null
    jsonNode.DeleteByNumber(2)
  end


Return Value

Returns jf:ok if a node was deleted. Returns jf:Error if the index number passed did not match an existing child.

See Also

DeleteByName

JSONClass

DisableField

DisableField ( Table | Queue | View | Group,
               String Name
             )

Description

Used to suppress a field from the structure when doing a Save.

Parameters

Parameter Description
Table / Queue / View / Group The structure to suppress the field from. This is typically the same structure as the following call to the Save method.
Name The name of the field to suppress. Note that this parameter is case sensitive. The name used here should be the name of the tag as it appears in the JSON output. In other words it is the External Name of the field not the field label.

Notes

This method should be called after the properties to RemovePrefix and ReplaceColons and set the TagCase are set.

Example

json.Start()
json.SetTagCase(jf:CaseLower)
json.SetRemovePrefix(true)
json.DisableField(Customers,'id')
json.Save(Customer,'customers.json')


Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then the field you attempted to disable was not found in the designated structure. Make sure the field is spelled correctly, is in the correct case and the call comes after any properties to adjust the field name are set.

See Also

Save

JSONClass

DisposeGroup

DisposeGroup(*Group pGroup,Long pDispose=false)

Description

Used to dispose a group that contains pointers. If you have loaded JSON into a group, that contains pointers, (such as described in loading Queues in Queues, Queues in Groups, and StringTheory in Groups and Queues ) then you need to be sure to correctly dispose the pointers in the group in order to prevent memory leaks. This method does all that needs to be done with a single call.

Parameters

Parameter Description
pGroup The name of the group to dispose.
pDispose If true then the group itself is disposed. This parameter is usually false.

Notes

This method should be called before the group goes out of scope.

Example

json.Start()
json.SetTagCase(jf:CaseLower)
json.Load(MessageGroup,str)
! do whatever with MessageGroup here
json.DisposeGroup(MessageGroup)

Return Value

This method does not return anything.

See Also

DisposeQueue

JSONClass

DisposeQueue

DisposeQueue(*Queue pQueue,Long pDispose=false)

Description

Used to dispose a queue that contains pointers. If you have loaded JSON into a group, that contains pointers, (such as described in loading Queues in Queues, Queues in Groups, and StringTheory in Groups and Queues ) then you need to be sure to correctly dispose the pointers in the queue in order to prevent memory leaks. This method does all that needs to be done with a single call.

Parameters

Parameter Description
pQueue The name of the queue to dispose.
pDispose If true then the queue itself is disposed. This parameter is usually false.

Notes

This method should be called before the group goes out of scope.

Example

json.Start()
json.SetTagCase(jf:CaseLower)
json.Load(MessageQueue,str)
! do whatever with MessageQueue here
json.DisposeQueue(MessageQueue)

Return Value

This method does not return anything.

See Also

DisposeGroup

JSONClass

Flatten

Flatten(*StringTheory pFlat)

Description

Populates a StringTheory object with a flat version of the JSON.
The flat version consists of each leaf in the JSON tree appearing on one line as;
name.name.name=value
 
Parameters

Parameter Description
pFlat A StringTheory object that will hold the flattened result.
Example

{
  "customer": {
    "name": "capesoft",
    "phone": "0878280123",
    "address": {
      "street": "Waterford Road",
      "city": "Cape Town"
     }
  }
}

becomes

customer.name="capesoft"
customer.phone="0878280123"
customer.address.street="Waterford Road"
customer.address.city="Cape Town"


Return Value

Returns nothing. The passed in StringTheory value holds the result.

See Also

ToXml, WalkNames

JSONClass

FormatValue

FormatValue(String pName, String pValue, *LONG pLiteralType)

Description

This method is called when a Clarion variable is being transformed into a JSON node. This method allows you to embed code so that the value stored in the JSON is formated in some way.

Parameters

Parameter Description
pName The name of the JSON node about to be stored.
pValue The raw Clarion value.
pLiteralType The JSON node type. If you are changing the type of the data (for example, changing the DATE from a Numeric to a String) then you need to change the LiteralType value as well. The value of this parameter should be one of
json:String  EQUATE(1)
json:Numeric EQUATE(2)
json:Object   EQUATE(3)
json:Array    EQUATE(4)
json:Boolean  EQUATE(5)
json:Nil      EQUATE(6)

Return Value

The formatted value of the raw value.

See Also

Formatting Field Values on Save, DeformatValue

JSONClass

Get

Get(Long pIndex, Long pClearReference = false)

Description

Gets a pointer to one of the child nodes of this object, based on the location in the current object.

Parameters

Parameter Description
pIndex The number of the child node to get.
pClearReference If this is set to true then the node is disconnected from the parent at this point. When the parent is cleared this child node will remain. The developer is responsible for DISPOSEing an object that has been disconnected from the parent in this way.
Return Value

Returns a pointer to a JsonClass.

See Also

GetByName, GetValue, GetArrayValue, GetValueByName
JSONClass

GetByName

GetByName(String pPropertyName, Long pLevelsAllowed=99999)

Description

Gets a pointer to one of the children of this Json object, based on the name of the child node.

Parameters

Parameter Description
pPropertyName The name of the node you wish to get a reference to.
pLevelsAllowed The number of levels deep that the search may go.
Return Value

Returns a pointer to a JsonClass.

See Also

Get, GetValue, GetArrayValue, GetValueByName
JSONClass

GetColumnDisabled

GetColumnDisabled(Long pColumn)

Description

Determines if a column is disabled or not.

Parameters

Parameter Description
pColumn The column number you wish to check.
Return Value

Returns false if the column is not disabled, or true if it is disabled.

See Also

SetColumnDisabled
JSONClass

GetName

GetName()

Description

Returns the name of the current object.

Return Value

Returns  a string containing the name of the current object.

See Also

GetValue, GetType
JSONClass

GetType

GetType()

Description

Returns the type of the current object.

Return Value

Returns a LONG value equivalent to one of
json:String, json:Numeric, json:Object, json:Array, json:Boolean or json:Null

See Also

GetName, GetValue
JSONClass

GetValue

GetValue(Long pRaw=false)

Description

Gets the value of the current object. If the object has children then the value will likely be blank.

Parameters

Parameter Description
pRaw If set to true then the value is passed through the DeformatValue method before being returned.
Return Value

A string containing the JSON value.

See Also

Get, GetByName, GetArrayValue, GetValueByName

GetArrayValue

GetArrayValue(Long pArrayIndex,Long pRaw=false)

Description

If the current object contains an array, then you can use this method to get a specific value from the array.

Parameters

Parameter Description
pArrayIndex The index into the array.
pRaw If set to true then the value is passed through the DeformatValue method before being returned.
Return Value

A string containing the JSON value.

See Also

Get, GetByName, GetValue, GetValueByName
JSONClass

GetValueByName

GetValueByName(String pPropertyName, Long pArrayIndex=1, Long pRaw=false)

Description

Gets the value of a named node. If the node is an array then a specific item in the array is returned.

Parameters

Parameter Description
pPropertyName The name of the node you are looking for.
pArrayIndex The index into that node (if the node is an array).
pRaw If set to true then the value is passed through the DeformatValue method before being returned.
Return Value

A string containing the JSON value.

See Also

Get, GetByName, GetValue, GetArrayValue , SetValueByName
JSONClass

InsertFileRecord

InsertFileRecord()

Description

This method is called when a record in a table is being added when doing a Load. This method does the ADD to add the record to the table.

Parameters

None

Notes

You would not usually call this method directly. However if you wish to do additional work just before, or just after a record is added, then this is a useful method to embed code into.

Return Value

The method returns jf:Ok if the ADD is successful, and the RecordsInserted property is incremented. If the ADD fails then the ErrorTrap method is called, and this method returns jf:ERROR.

See Also

UpdateFileRecord, AddQueueRecord

JSONClass

Load

Load (Table | Queue | Group,
      <String FileName> | <StringTheory Buffer> ,
      <String Boundary>
     )

Load ( StringTheory Buffer,
            <Long Append>
     )

Description

Loads a Clarion structure from a JSON text file, StringTheory buffer, or internal JSON structure. Is the primary method used for parsing JSON structures into something that can be used directly by the program.

The second form of the method is the same as calling LoadString (see that method for details.)

Parameters

Parameter Description
Table / Queue /  Group The structure to Load into.
FileName / Buffer If a string is passed here then this is the name of the file to load from disk. If a StringTheory object is passed here, then the JSON inside the string is loaded. If this parameter is omitted and the boundary is omitted then the internal JSON object will be loaded into the structure.
If this parameter is an empty string, and a boundary is set,
Boundary (optional) If the boundary is included then the subset of the JSON source, with that name, will be loaded.
If the boundary is included then the second parameter cannot be omitted, it must be a blank string if the json object already contains the data to be loaded.

Notes


Example

json.Start()
json.SetTagCase(jf:CaseLower)
json.Load(Customer,'customers.json')


Example

json.start()
json.loadString(str)
json.load(CustomerQueue,'','customers')


Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then additional information can be found in the Error property.

See Also

Save, LoadString, LoadFile

JSONClass

LoadFile

LoadFile( <String FileName>,
          <Long Append>,
        )


Description

Loads a JSON file from the disk into the internal JSON object.

Parameters

Parameter Description
FileName  This is the name of the file to load from the disk.
Append (optional) False by default. If True then the internal JSON file is cleared before doing the Load.

Notes

This method constructs the internal JSON object from a source file. It is not usually called directly, but from the Load method. However if you are loading only to the object and not a structure, then use this method.

Example

json.start()
json.LoadFile('customers.json')
json.SaveFile('customers.json',true)

Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then additional information can be found in the Error property.

See Also

Load, LoadString

JSONClass

LoadNodes

LoadNodes( QUEUE pQueue, String pChildNodeName, <*? pQueueParent>, <String pIdNode>, <String pParentValue> )

Description

Loads the internal Json object into a Queue, using a recursive walk rather than simply importing a list.

Parameters

Parameter Description
pQueue The Queue to load the JSON into.
pChildNodeName The name of the node that leads to more items like itself.
pQueueParent The queue field which will store the parent ID value.
pIdNode The (string) name of the node which contains the ID value.
pParentValue The parent ID value of the first node in the Json. This is typically used internally and  omitted when making the call from a program.

Notes

The use of this method is explained in the section Importing Recursive Nodes.

Return Value

The method returns nothing. Records may be added to the pQueue parameter if matches are found.

See Also



JSONClass

LoadString

LoadString( StringTheory Buffer | String Buffer,
            <Long Append>
          )


Description

Loads the internal JSON object from a StringTheory string or normal Clarion string,.

Parameters

Parameter Description
Buffer The name of a StringTheory object, or string variable, which contains the JSON text.
Append (optional) If omitted the default is false. If set to true then the JSON is added to the current contents of the JSON object. If set to false then the JSON object is cleared before the new JSON is added.

Notes

This method constructs the internal JSON object from a source string. It is not usually called directly, but from the Load method. However if you are loading only to the object and not a structure, then use this method.

Example

str  StringTheory
  code
  json.start()
  json.LoadString(str,true)

Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then additional information can be found in the Error property.

See Also

Load, LoadFile

JSONClass

Merge

Merge(JSONClass pJson)

Description

Merges another document into this document, at the top level.

Parameters

Parameter Description
pJson The name of the other document to merge into this document

Notes

This merges the pJson document passed as a parameter into the current document. The current document is altered. The merge is done at the "first level" and all the remote nodes are imported, even if they are identical to the current nodes.

Example

json1 document
{ "names": [
  {"firstname":"bill", "lastname":"gates"},
  {"firstname":"steve", "lastname":"jobs" }
],
"offices":{
  "name":"pinelands",
  "security":"yes",
  "boom":"yes"
},
"departments":["sales","support"]
}

json2 document
{ "names": [
  {"firstname":"larry", "lastname":"page"},
  {"firstname":"segei", "lastname":"brin" }
],
"departments":["search","advertising"],
"offices":{
  "name":"diep river",
  "security":"no",
  "lift":"yes"
},
"branches":[
  {"sales":"cape town"},
  {"support":"johannesburg"}
}

Code
json1  jsonClass
json2  jsonClass
code
  json1.start()
  json2.start()
  json1.LoadFile('json1 document')
  json2.LoadFile('json2 document')
  json1.merge(json2)

Json1 document after merge
{ "names" : [
    { "firstname" : "bill", "lastname" : "gates"},
    { "firstname" : "steve","lastname" : "jobs"},
    { "firstname" : "larry","lastname" : "page"},
    { "firstname" : "segei","lastname" : "brin"}
  ],
  "offices" : {
    "name" : "pinelands",
    "security" : "yes",
    "boom" : "yes",
    "name" : "diep river",
    "security" : "no",
    "lift" : "yes"
  },
  "departments" : [ "sales","support","search","advertising"],
  "branches" : [
    { "sales" : "cape town" },
    { "support" : "johannesburg"}
  ]
}

Return Value

The method returns nothing. The contents of the current class are altered.

See Also



JSONClass

NewPointer

NewPointer(String pColumnName)

Description

Provides a place for derived classes to can NEW for specific QUEUE data structures, or other Pointers.

Parameters

Parameter Description
pColumnName The name of the column which needs to be NEWed

Notes

This method is used when loading queues in queues. It is used to NEW the child data structure.

Example

json               class(jsonClass)
NewPointer           procedure(String pColumnName),Derived
                   end

DiscountsQueueType Queue,Type
DiscountType         String(20),name('DiscountType')
Amount               Decimal(10,2),name('Amount')
Description          &StringTheory,name('Description | stringtheory')
                   End

MessagesQ          Queue
Id                   Long,name('Id')
DiscountsQ           &DiscountsQueueType,name('Discounts | queue')
                   End

  code
  json.start()
  json.Load(MessagesQ,str)

json.NewPointer Procedure(String pColumnName)
  code
  case pColumnName
  of 'Discounts'
    MessagesQ.Data.DiscountsQ &= NEW DiscountsQueueType
  end

Return Value

Nothing

See Also

Loading a Queue within a Queue

JSONClass

Save

Save (Table | Queue | View | Group,
      <String FileName> | <StringTheory Buffer>,
      <String Boundary>,
      <Long Format>,
      <Long Compressed>,
      <Long Loop>
     )


Description

Saves a Clarion structure to a JSON text file, StringTheory buffer, or internal JSON structure. Is the primary method used for creating simple JSON text which can be consumed by another program.

Parameters

Parameter Description
Table / Queue / View / Group The structure to Save.
FileName / Buffer If a string is passed here then this is the name of the file to save on disk. If a StringTheory object is passed here, then the resulting JSON will be placed in the StringTheory object (and not saved to disk). If this parameter is omitted then the internal JSON object will be created, but not saved to disk or a string.
Boundary (optional) If the boundary is included then the created JSON is created as a named object, with the Boundary parameter forming the name. If it is omitted then the created JSON is unnamed.
Format (optional) Optional - can be jf:format or jf:noformat, defaults to jf:format. If this parameter is set to jf:format then the output is formatted suitable for reading by a human. It contains whitespace which is ignored by other programs. Setting this to jf:noformat creates a slightly smaller string or file.
Compressed (optional) This parameter is only applied if the second parameter is a FileName. If this parameter is omitted then it defaults to false. If set to jf:compressed then the file is compressed (using gzip) before it is saved to the disk.
Loop
(optional)
This parameter only applies when the first parameter is a Table, Queue or View. If the first parameter is a Group then the parameter can still be set, but has no effect. The default value for this field is jf:loop.
If set to jf:noloop the Save does not iterate over the structure. In other words it does not save all the records in the structure, ONLY the currently-loaded record. The structure is not opened or closed (Tables and Views), and ValidateRecord is not called.

Notes

Typically the json file created will be set as an Array (for Table, Queue and View structures) regardless of the number of items in the list. If there is only one item (or no items) you may wish to change this so the boundary tag is an Object instead of an array. You can do this by using the SetType method.

Example

json.Start()
json.SetTagCase(jf:CaseLower)
json.Save(Customer,'customers.json')


Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then additional information can be found in the Error property.

See Also

DisableField, SetType

JSONClass

SaveFile

SaveFile( <String FileName>,
          <Long Format>,
          <Long Compressed>
        )


Description

Saves the internal JSON object to a file on the disk.

Parameters

Parameter Description
FileName  This is the name of the file to create on the disk.
Format (optional) Optional - can be true or false, defaults to true. If this parameter is set to true then the output is formatted suitable for reading by a human. It contains whitespace which is ignored by other programs. Setting this to false creates a slightly smaller file
Compressed (optional) If this parameter is omitted then it defaults to false. If set to true then the file is compressed (using gzip) before it is saved to the disk.

Notes

This method does not construct the JSON object at all - by the time you call this it is assumed that the JSON object has been created using the other methods (Save, Load, Append and so on.)

Example

json.Start()
json.LoadFile('customers.json')
json.SaveFile('customers.json',true)

Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then additional information can be found in the Error property.

See Also

Save

JSONClass

SaveString

SaveString( <StringTheory Buffer>,
            <Long Format>,
            <Long Level>,
            <Long Append>
          )


Description

Saves the internal JSON object to a StringTheory string.

Parameters

Parameter Description
Buffer The name of a StringTheory object which will contain the result.
Format (optional) Optional - can be true or false, defaults to false. If this parameter is set to true then the output is formatted suitable for reading by a human. It contains whitespace which is ignored by other programs. Setting this to false creates a slightly smaller file
Level (optional) If omitted the default is 0. Sets the indent level for the string.
Append (optional) If omitted the default is false. If set to true then the json is added to the current contents of the StringTheory object. If set to false then the StringTheory object is cleared before the json is added.

Notes

This method does not construct the JSON object at all - by the time you call this it is assumed that the JSON object has been created using the other methods (Save, Load, Append and so on.)

Example

str  StringTheory
  code
  json.Start()
  json.LoadFile('customers.json')
  json.SaveString(str,true)

Return Value

The method returns either jf:OK or jf:ERROR. If jf:ERROR then additional information can be found in the Error property.

See Also

Save

JSONClass

SetColumnDisabled

SetColumnDisabled(Long pColumn, Long pValue)

Description

Sets if a column is disabled or not.

Parameters

Parameter Description
pColumn The column number you wish to set.
pValue The disabled state of the column. Should be either TRUE or FALSE.
Return Value

Nothing

See Also

GetColumnDisabled
JSONClass

SetColumnType

SetColumnType(*Group pGroup, String pColumnName, Long pFieldType)

Description

This is typically used when a non-standard type, or pointer, exists in the structure. This method must be called to identify those fields so they can be handled by the AddByReference method.

It is also used to change the type of a field from the Clarion data type to a different Json data type. For example, In clarion a date is stored in a LONG, but is often formatted in JSON as a string. So setting the type to json:string allows the class to know the specific type to use.

Parameters

Parameter Description
pGroup The group that contains the field.
pColumnName The name of the field. This is the JSON name (as it will appear in the output) and is case sensitive.
pfieldType The type of the field. Usually json:Reference. Or json:string, json:Numeric.
Return Value

Nothing

See Also

Saving Nested Structures - yet another approach , AddByReference
JSONClass

SetTagCase

SetTagCase(Long pTagCase)

Description

Sets the TagCase property for this object and all the currently existing child objects as well. If the TagCase property of an object needs to be changed, and the object already contains data, then it is better to use this method than to set the property directly.

If the object does not contain any existing children then it is safe to use this method. So using this method to set the property is recommended for almost all cases where the the TagCase property is being set. The exception is if you wish to set the property for one node, but not for the child nodes of that node.


Parameters
Parameter Description
pTagCase One of jF:CaseLower, jF:CaseUpper, jF:CaseAsIs, jf:CaseAny

Example

json.SetTagCase(jf:CaseAsIs)

Notes

Setting to jf:CaseAsIs uses the External name attribute for each field to determine the case. If there is no external name attribute then the UPPER case of the Label is used. The case of the Label itself is not used.

Return Value

Nothing


JSONClass

SetType

SetType(Long pType)

Description

Sets the ObjectType property for this object.

The ObjectType is usually set automatically by a call to Save and is set to an appropriate value based on the structure being saved. Tables, Views and Queue will be of type json:Array while groups will be of type json:Object.

It is sometimes desirable to override the default setting, forcing the type to say json:Object if there is only one (or none) items in the list, or forcing a Group to be saved as a json:Array (containing a single item.)

In a small number of cases, where the JSON output will only contain one, unnamed, value, setting the type to json:null will prevent the file boundary tags from being written at all.

The SetType method can be called before a call to Save in order to achieve this.


Parameters
Parameter Description
pType One of json:Object, json:Array, json:null

Example

json.Start()
if records(myQueue) <= 1
  json.SetType(json:Object)
end
json.save(myQueue,'result.json')

Notes


Return Value

Nothing


JSONClass

SetValue

SetValue(String pValue, Long pLiteralType = json:String, Long pRaw=false)

Description

Set the value of the node in the JSON.

Parameters
Parameter Description
pValue The value to set the node to.
pLiteralType Defaults to json:String. Can be one of json:String, json:Numeric, json:Boolean, json:Null, json:Json
pRaw If set to true then demomode is not respected, and the FormatValue method is not called before placing in the JSON.

Example

json.SetValue('CapeSoft')

Return Value

Nothing

See Also

SetValueByName, GetValue, GetValueByName

JSONClass

SetValueByName

SetValueByName(String pPropertyName, String pValue, Long pLiteralType = json:String, Long pRaw=false, Long pLevels=99999)

Description

Set the value of a node in the JSON. If the node already exists (matching on the name) then the value is updated. If the node does not exist then it is added at the top level.

Parameters
Parameter Description
pPropertyName The name of the node to add, or update.
pValue The value to set the node to.
pLiteralType Defaults to json:String. Can be one of json:String, json:Numeric, json:Boolean, json:Null, json:Json
pRaw If set to true then demomode is not respected, and the FormatValue method is not called before placing in the JSON.
pLevels Limits the search for the existing property name to the specified number of levels.

Example

json.start()
json.setTagCase(jf:caseAsIs)
json.SetNestViewRecords(true)
json.setViewBoundary(Invoice_view,LineItems,'Line Items',jf:OneToMany) 
json.setViewBoundary(Invoice_view,Product,'Product',jf:ManyToOne)      
json.save(Invoice_view,str,'Invoices')

Return Value

Returns jf:ok

See Also

SetValue, GetValueByName

JSONClass

SetViewBoundary

SetViewBoundary(View pView, *File pTable, String pBoundary,Long pRelation)

Description

Used when exporting a nested view structure to a nested JSON structure.

Parameters
Parameter Description
pView The name of the VIEW that this boundary applies to.
pTable The JOINed table in the VIEW.
pBoundary The boundary text to use, in the JSON output, to identify the child JSON node.
pRelation One of jf:OneToMany or jf:ManyToOne. Use OneToMany if the child node should be a list, or ManyToOne if the child node should be a group. From a cosmetic point of view you can use jf:list in place of jf:OneToMany and jf:lookup in place of jf:ManyToOne.

Example

json.Start()
! other commands go here.


Return Value

Nothing

See Also

Creating Nested JSON structures from a View, NestViewRecords

JSONClass

Start

Start()

Description

Used to return the object to its default state. Allows the object to be reused without worrying about properties set in an earlier use still being set for this use.

Example

json.Start()
! other commands go here.


Return Value

Nothing

JSONClass

ToXML

ToXML(StringTheory pXML, Long pIndent, String pName, String pRoot)

Description

Saves the JSON as XML. Json does not have element names, or root names, so these need to be supplied via parameters.

Parameters

Parameter Description
pXML The StringTheory object which will be set to contain the result. The result will be APPENDed to the existing contents of the StringTheory object.
pIndent The indentation to use for the first XML tag in the result.
pName The name to use for all elements in the xml, which do not have a name in the JSON.
pRoot The name to use for the first (root) node in the XML output.

Example


json.start()
json.SetTagCase(jf:CaseAsIs)
json.LoadFile('somefile.json')
json.ToXml(str,0,'element','xml')


Notes

JSON does not directly translate to XML because in XML every node needs a valid name, whereas in JSON Array Values do not have names. Also XML has to have a root node name, whereas JSON does not. The pName and pRoot parameters allow arbitrary node names to be used.

Return Value

Nothing

See Also

WalkNames, Flatten

JSONClass

Trace

Trace(String Str)

Description

A debugging method which sends the passed string to the DebugView debugging program.

Parameters

Parameter Description
str The string to send to DebugView

Example


json.Trace('some string goes here')

Notes

Debugview, UltimateDebug or DebugView++ are tools used for debugging that can display messages from the application while it is running, and without interrupting program flow.

Return Value

Nothing

See Also

WalkNames

JSONClass

UpdateFileRecord

UpdateFileRecord()

Description

This method is called when a record in a table is being updated when doing a Load. This method does the PUT to put the record to the table.

Parameters

None

Notes

You would not usually call this method directly. However if you wish to do additional work just before, or just after a record is updated, then this is a useful method to embed code into.

Return Value

The method returns jf:Ok if the PUT is successful, and the RecordsUpdated property is incremented. If the PUT fails then the ErrorTrap method is called, and this method returns jf:ERROR.

See Also

InsertFileRecord, AddQueueRecord

JSONClass

ValidateField

ValidateField (StringTheory pFieldName, Long pFieldNumber)

Description

When looping through a structure, this method is called for each field for each record in the structure. This method is used only for SAVE loops.

Parameters


Parameter Description
pFieldName The name of the field. Note this is the JSON tag name, not the Clarion field name. This parameter is case sensitive.
pFieldNumber An alternative way to identify the field - this is the field number of the field in the parent (Clarion) structure.

Notes

You would not usually call this method directly. However if you wish to do additional work to filter out fields on a runtime basis (ie on a row by row basis), then this is the place to embed your filtering code. If you want to exclude a field from all rows then use the DisableField method instead.

Return Value

The class method is empty, and always returns jf:OK.
If you add code to this method you should return one of;
JF:OK - The record is ok and can be processed
JF:OutOfRange - This field, and all subsequent fields in this one record should be excluded
JF:Filtered - Just this field should be excluded, continue processing more fields (which in turn will be passed through this method.)

See Also

DisableField, ValidateRecord

JSONClass

ValidateRecord

ValidateRecord()

Description

When looping through a structure, this method is called for each record in the structure. This method is used for both LOAD and SAVE loops.

Parameters


None

Notes

You would not usually call this method directly. However if you wish to do additional work to filter out records, then this is the place to embed your filtering code.
You can use the GetAction method to get the Action property, to determine if the current task is a  jf:Save or jf:Load

Return Value

The default  class method is empty, and always returns jf:OK.

If you add code to this method you should return one of;
JF:OK - The record is ok and can be processed
JF:OutOfRange - This record, and all subsequent records should be excluded
JF:Filtered - Just this record should be excluded, continue processing more records (which in turn will be passed through this method.)

See Also

Save, Load, ValidateField, Filtering Records When Loading

JSONClass

WalkNames

WalkNames(Long pIndent=0, Long pOptions=jf:showValue)

Description

This is a debugging method that send the contents of the current object, and child objects, to Debugview.
This allows you to visualize the current contents of the JSON structure.

Parameters


Parameter Description
pIndent An amount of spaces to indent the text by. Defaults to 0 for the top level (and increases for each level).
pOptions Contains one of jf:NoValuesjf:ShowValue, or jf:Raw.
If jf:NoValues is used then node names are displayed, but not the values.
If jf:ShowValue (the default) then the value of the node id displayed, formatted with any formatting information known about the node.
If jf:Raw is used then the raw value, with no formatting, is displayed.
Example

json  JsonClass
  code
  json.LoadString(str)
  json.WalkNames()


Notes

Debugview, UltimateDebug or DebugView++ are tools used for debugging that can display messages from the application while it is running, and without interrupting program flow.

Return Value

Nothing

See Also

Trace, Flatten, ToXML

Frequently Asked Questions

  1. In a Multi-DLL set of apps,  after updating jFile, I am getting an Unresolved External error in some of my apps

    Go to the Data DLL for this system, go to the jFiles global extension, to the Classes tab and click on the button RefreshClasses.
    Then recompile the whole set of apps.

Support

Your questions, comments and suggestions are welcome. See our web page (www.capesoft.com) for new versions. You can also contact us in one of the following ways:
CapeSoft Support
Email
Telephone +27 (0)87 828 0123

Installation

Run the supplied installation file.

Distribution

This product is supplied as source files that are included in your application. There are no additional files for you to add to your distribution.

License

This template is copyright © 2021 by CapeSoft Software. None of the included files may be distributed. Your programs which use this product can be distributed without any royalties due on this product.

Each developer needs his own license to use this product. (Need to buy more licenses?)

This product is provided as-is. Use it entirely at your own risk. Use of this product implies your acceptance of this, along with the recognition of the copyright stated above. In no way will CapeSoft Software, their employees or affiliates be liable in any way for any damages or business losses you may incur as a direct or indirect result of using this product

Version History

Version 2.22 - 24 May 2021
Version 2.21 - 3 March 2021 Version 2.20 - 15 February 2021 Version 2.19 - 26 January 2021 Version 2.18 - 30 December 2020 Version 2.17 - 19 November 2020 Version 2.16 - 17 November 2020 Version 2.15 - 6 October 2020 Version 2.14 - 28 September 2020  Version 2.13 - 1 September 2020  Version 2.12 - 22 August 2020  Version 2.11 - 13 August 2020  Version 2.10 27 - July 2020  Version 2.09  -16 June 2020 Version 2.08 - 21 April 2020  Version 2.07 - 23 March 2020
Version 2.06 (30 December 2019)  Version 2.05 (23 December 2019)  Version 2.04 (4 December 2019)  Version 2.03 (11 October 2019)  Version 2.02 (17 September 2019)  Version 2.01 (4 September 2019)  Version 2.00 (30 Aug 2019)  Version 1.86 (2 Aug 2019)  Version 1.85 (30 July 2019)  Version 1.84 (26 July 2019)  Version 1.83 (18 July 2019)  Version 1.82 (16 July 2019)  Version 1.81 (12 July 2019)  Version 1.80 (9 July 2019)  Version 1.79 (9 May 2019)  Version 1.78 (6 May 2019)  Version 1.77 (3 May 2019)  Version 1.76 (2 May 2019)  Version 1.75 (29 April 2019)  Version 1.74 (1 March 2019)  Version 1.73 (13 Feb 2019)  Version 1.72 (22 Nov 2018)  Version 1.71 (13 Sept 2018)  Version 1.70 (13 Sept 2018)  Version 1.69 (27 July 2018)  Version 1.68 (24 July 2018)  Version 1.67 (16 July 2018)  Version 1.66 (11 July 2018)  Version 1.65 (9 July 2018)  Version 1.64 (31 May 2018)  Version 1.63 (19 April 2018)  Version 1.62 (16 April 2018)  Version 1.61 (12 April 2018)  Version 1.60 (23 March 2018)  Version 1.59 (20 March 2018)  Version 1.58 (25 January 2018)  Version 1.57 (12 December 2017)  Version 1.56 (13 November 2017)  Version 1.55 (7 November 2017)  Version 1.54 (30 October 2017)  Version 1.53 (23 October 2017)  Version 1.52 (26 September 2017)  Version 1.51 (1 September 2017)  Version 1.50 (30 August 2017)  Version 1.49 (22 August 2017)  Version 1.48 (4 August 2017)  Version 1.46 (21 July 2017) 
Version 1.45 (18 July 2017)
  Version 1.44 (7 July 2017)  Version 1.43 (15 May 2017)  Version 1.42 (12 May 2017) 
Version 1.41 (5 May 2017)  Version 1.40 (3 May 2017)  Version 1.39 (4 April 2017)  Version 1.38 (4 April 2017)  Version 1.37 (24 March 2017)  Version 1.36 (9 March 2017)  Version 1.35 (8 March 2017)  Version 1.34 (1 February 2017)  Version 1.33 (25 January 2017)  Version 1.32 (12 January 2017)  Version 1.31 (9 January 2017)  Version 1.30 (29 December 2016)  Version 1.29 (16 November 2016)  Version 1.28 (19 September 2016)  Version 1.27 (16 September 2016)  Version 1.26 (6 September 2016)  Version 1.25 (1 September 2016)  Version 1.24 (17 August 2016)  Version 1.23 (25 July 2016)  Version 1.22 (11 April 2016)  Version 1.21 (6 April 2016)  Version 1.20 (5 April 2016)  Version 1.19 (23 February 2016)  Version 1.18 (23 February 2016)  Version 1.17 (23 November 2015)  Version 1.16 (23 November 2015)  Version 1.15 (18 November 2015)  Version 1.14 (17 November 2015)  Version 1.13 (7 October 2015)  Version 1.12 (22 July 2015) -- Requires StringTheory 2.33 or later. Version 1.11 (24 July 2015) -- Requires StringTheory 2.33 or later. Version 1.10 (3 July 2015)  Version 1.09 (30 June 2015)  Version 1.08 (24 June 2015)  Version 1.07 (8 June 2015)  Version 1.06 (11 May 2015)  Version 1.05 (29 April 2015)  Version 1.04 (16 April 2015)  Version 1.03 (9 April 2015)  Version 1.02 (2 April 2015)  Version 1.01 (31 March 2015)  Version 1.00 (18 March 2015)