4 - JSON und der goldene Converter / JSON and the golden converter
Bereits im vorherigen Kapitel haben wir eine Möglichkeit gesehen wie wir Daten in ein JSON Gerüst einbetten können:
TEXT TO lcJSONData TEXTMERGE NOSHOW PRETEXT 2+4+8
"int1" : <<myCursor.Column1>>,
"int2" : <<myCursor.Column2>>,
"text1" : "<<ALLTRIM( myCursor.Column3 )>>",
"int3" : <<myCursor.Column4>>,
"text2" : "<<ALLTRIM( myCursor.Column5 )>>"
ENDTEXT
lcJSONData = [{] + URIConverter( lcJSONData ) + [}]
Nun ist diese kleine Routine zum Erstellen eines JSON Streams noch recht übersichtlich. Bei umfangreicheren Übergabedaten macht es durchaus Sinn einen spezialisierten Cursor zu erstellen und diesen an eine Methode zu übergeben, die aus dem Cursor ein JSON Konstrukt generiert. Voraussetzung ist, dass die Spaltennamen mit den vom Webservice erwarteten Feldnamen des JSON Streams korrespondieren.
Nehmen wir einmal die folgenden Daten als Grundlage:
Gehen wir nun davon aus, dass der Webservice als Feldnamen int1, int2, text1, int3 und text2 erwartet (dies entspricht letztlich dem ersten oben zu sehenden JSON Konstrukt), so kann mit einem einfachen SELECT die benötigte Struktur bzw. Benamung erzeugt werden.
SELECT Column1 AS int1, Column2 AS int2, Column3 AS text1, Column4 AS int3, Column5 AS text2 ;
FROM myRealData ;
INTO CURSOR myJSONcursor ;
READWRITE
Dieser neu erstellte
Cursor wird nun an eine Funktion übergeben deren Aufgabe darin
besteht, aus diesen Daten einen JSON String zu erstellen. Hierbei nimmt die Funktion zwei
Parameter entgegen.
Parameter 1 ist der Name des zu verarbeitetenden Cursors und der optionale Parameter zwei erzwingt eckige Klammern die das Konstrukt umgeben. Bei mehr als einem Datensatz erfolgt dies allerdings automatisch.
Über den zweiten Parameter kann auch ein einfacher Array (also keine Name:Wert Paare) erzeugt werden. Dies ist in der Funktion jedoch nur zulässig, wenn der übergebene Cursor maximal einen Datensatz enthält. Andernfalls wird der Parameterwert 2 auf den Wert 1 zurückgesetzt.
Parameter #2
=0 (default)
Der generierte JSON String erhält automatisch eckigen Klammern wenn der übergebene Cursor mehr als einen Datensatz enthält.
=1
Erzwingt eckige Klammen, unabhängig von der Anzahl der Datensätze
=2
Erzeugt einen einfachen JSON Array. D.h. es werden keine Name:Wert Paare gebildet sondern eine einfache komma-separierte Wertliste die in eckige Klammern gepackt wird.
Parameter 1 ist der Name des zu verarbeitetenden Cursors und der optionale Parameter zwei erzwingt eckige Klammern die das Konstrukt umgeben. Bei mehr als einem Datensatz erfolgt dies allerdings automatisch.
Über den zweiten Parameter kann auch ein einfacher Array (also keine Name:Wert Paare) erzeugt werden. Dies ist in der Funktion jedoch nur zulässig, wenn der übergebene Cursor maximal einen Datensatz enthält. Andernfalls wird der Parameterwert 2 auf den Wert 1 zurückgesetzt.
Parameter #2
=0 (default)
Der generierte JSON String erhält automatisch eckigen Klammern wenn der übergebene Cursor mehr als einen Datensatz enthält.
=1
Erzwingt eckige Klammen, unabhängig von der Anzahl der Datensätze
=2
Erzeugt einen einfachen JSON Array. D.h. es werden keine Name:Wert Paare gebildet sondern eine einfache komma-separierte Wertliste die in eckige Klammern gepackt wird.
* // Param #1: Name of Cursor
* // Param #2: Mode
* // 0 = default
* // this means, the JSON String gets square
* // brackets if more than one record is found.
* // 1 = force square brackets
* // 2 = create JSON array only
* // this means, only comma separated values
* // packed in square brackets
* // IMPORTANT: only allowed when cursor has ONE
* // single record. Otherwise an object array has
* // to be created!!!
FUNCTION Cursor2JSON as String
LPARAMETERS vCrsName as String, vForceSquareBrackets as Integer
m.vForceSquareBrackets = EVL( m.vForceSquareBrackets , 0 )
* // pure JSON Array only allowed bei single records
IF m.vForceSquareBrackets = 2 AND RECCOUNT( m.vCrsName ) > 1
m.vForceSquareBrackets = 1
ENDIF
LOCAL lcJSON as String, liLoop as Integer
SELECT ( m.vCrsName )
AFIELDS( arrName , m.vCrsName )
lcJSON = []
* // loop through records
SELECT( vCrsName )
GO TOP
DO WHILE NOT EOF( vCrsName )
IF m.vForceSquareBrackets <> 2
lcJSON = lcJSON + IIF( RECNO( vCrsName ) > 1 , [,{] , [{] )
ENDIF
* // loop through columns
FOR liLoop = 1 TO ALEN( arrName , 1 )
* // from 2nd run on we have to add a comma as seperator
lcJSON = lcJSON + IIF( liLoop > 1 , [,] , [] )
* // now add the columnname
IF m.vForceSquareBrackets <> 2
lcJSON = lcJSON + ["] + ALLTRIM( LOWER( arrName( liLoop , 1 ) ) ) + ["] + [:]
ENDIF
luValue = EVALUATE( m.vCrsName + [.] + ALLTRIM( arrName( liLoop , 1 ) ) )
* // now add the columvalue depending on its vartype
DO CASE
CASE arrName( liLoop , 2 ) = [C] && Character
lcJSON = lcJSON + ["] + ALLTRIM( luValue ) + ["]
CASE arrName( liLoop , 2 ) = [Y] && Money
lcJSON = lcJSON + CHRTRAN( TRANSFORM( luValue ) . [,] , [.] )
CASE arrName( liLoop , 2 ) = [D] && Date
lcJSON = lcJSON + ["] + DTOC( luValue ) + ["]
CASE arrName( liLoop , 2 ) = [T] && Datetime
lcJSON = lcJSON + ["] + TTOC( luValue , 3 ) + [.000Z] + ["]
CASE arrName( liLoop , 2 ) = [F] && Float
lcJSON = lcJSON + CHRTRAN( TRANSFORM( luValue ) . [,] , [.] )
CASE arrName( liLoop , 2 ) = [I] && Integer
lcJSON = lcJSON + TRANSFORM( luValue )
CASE arrName( liLoop , 2 ) = [L] && Logical
lcJSON = lcJSON + TRANSFORM( IIF( luValue = .T. , [true] , [false] ) )
CASE arrName( liLoop , 2 ) = [N] && Numeric
lcJSON = lcJSON + CHRTRAN( TRANSFORM( luValue ) . [,] , [.] )
ENDCASE
ENDFOR
IF m.vForceSquareBrackets <> 2
lcJSON = lcJSON + [}]
ENDIF
SKIP IN ( m.vCrsName )
ENDDO
* // in case of multiple records, we have to use squarebrackets
* // as each record is a JSON object
IF RECCOUNT( vCrsName ) > 1 OR m.vForceSquareBrackets > 0
lcJSON = "[" + lcJSON + "]"
ENDIF
RETURN lcJSON
ENDFUNC
Auf diese Weise können auch mehrere in Relation
stehende Cursor verschachtelt in einen JSON String gepackt werden.
Im folgenden mal ein paar Codebeispiele, wie diese Funktion genutzt werden kann:
CLEAR
* // cleanup
USE IN SELECT( [crsMydata] )
USE IN SELECT( [myJSONcursor] )
* // creating basic data
CREATE CURSOR crsMydata ( column1 i, column2 i, column3 c(10), column4 i, column5 c(10))
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 1 , 11 , [aaa] , 1111 , [a] )
* // convert to JSON capability
SELECT column1 AS int1, column2 AS int2, column3 AS text1, column4 AS int3, column5 AS text2 ;
FROM crsMydata ;
INTO CURSOR myJSONcursor ;
READWRITE
* // now convert to JSON
lcJSONdata = Cursor2JSON( [myJSONcursor] )
?[---------------------------------------------- ONE record as JSON object/collection]
?lcJSONdata
* // cleanup
USE IN SELECT( [crsMydata] )
USE IN SELECT( [myJSONcursor] )
* // creating basic data
CREATE CURSOR crsMydata ( column1 i, column2 i, column3 c(10), column4 i, column5 c(10))
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 1 , 11 , [aaa] , 1111 , [a] )
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 2 , 22 , [bbb] , 2222 , [ab] )
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 3 , 33 , [ccc] , 3333 , [abc] )
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 4 , 44 , [ddd] , 4444 , [abcd] )
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 5 , 55 , [eee] , 5555 , [abcde] )
* // convert to JSON capability
SELECT column1 AS int1, column2 AS int2, column3 AS text1, column4 AS int3, column5 AS text2 ;
FROM crsMydata ;
INTO CURSOR myJSONcursor ;
READWRITE
* // now convert to JSON
lcJSONdata = Cursor2JSON( [myJSONcursor] , 1 )
?[---------------------------------------------- MULTIPLE records as JSON object array]
?lcJSONdata
* // cleanup
USE IN SELECT( [crsMydata] )
USE IN SELECT( [myJSONcursor] )
* // creating basic data
CREATE CURSOR crsMydata ( column1 i, column2 i, column3 i, column4 i, column5 i)
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( 1 , 11 , 111 , 1111 , 11111 )
* // convert to JSON capability
SELECT column1 AS int1, column2 AS int2, column3 AS int3, column4 AS int4, column5 AS int5 ;
FROM crsMydata ;
INTO CURSOR myJSONcursor ;
READWRITE
* // now convert to JSON
lcJSONdata = Cursor2JSON( [myJSONcursor] , 2 )
?[---------------------------------------------- ONE record as JSON array]
?lcJSONdata
* // cleanup
USE IN SELECT( [crsMydata] )
USE IN SELECT( [myJSONcursor] )
* // creating basic data
CREATE CURSOR crsMydata ( column1 C(1), column2 C(2), column3 c(3), column4 c(4), column5 c(5))
INSERT INTO crsMydata ( column1 , column2 , column3 , column4 , column5 ) VALUES ( [a] , [aa] , [aaa] , [aaaa] , [aaaaa] )
* // convert to JSON capability
SELECT column1 AS text1, column2 AS text2, column3 AS text3, column4 AS text4, column5 AS text5 ;
FROM crsMydata ;
INTO CURSOR myJSONcursor ;
READWRITE
* // now convert to JSON
lcJSONdata = Cursor2JSON( [myJSONcursor] , 2 )
?[---------------------------------------------- ONE record as JSON array]
?lcJSONdata
* // Param #1: Name of Cursor
* // Param #2: Mode
* // 0 = default
* // this means, the JSON String gets square
* // brackets if more than one record is found.
* // 1 = force square brackets
* // 2 = create JSON array only
* // this means, only comma separated values
* // packed in square brackets
* // IMPORTANT: only allowed when cursor has ONE
* // single record. Otherwise an object array has
* // to be created!!!
FUNCTION Cursor2JSON as String
LPARAMETERS vCrsName as String, vForceSquareBrackets as Integer
m.vForceSquareBrackets = EVL( m.vForceSquareBrackets , 0 )
* // pure JSON Array only allowed bei single records
IF m.vForceSquareBrackets = 2 AND RECCOUNT( m.vCrsName ) > 1
m.vForceSquareBrackets = 1
ENDIF
LOCAL lcJSON as String, liLoop as Integer
SELECT ( m.vCrsName )
AFIELDS( arrName , m.vCrsName )
lcJSON = []
* // loop through records
SELECT( vCrsName )
GO TOP
DO WHILE NOT EOF( vCrsName )
IF m.vForceSquareBrackets <> 2
lcJSON = lcJSON + IIF( RECNO( vCrsName ) > 1 , [,{] , [{] )
ENDIF
* // loop through columns
FOR liLoop = 1 TO ALEN( arrName , 1 )
* // from 2nd run on we have to add a comma as seperator
lcJSON = lcJSON + IIF( liLoop > 1 , [,] , [] )
* // now add the columnname
IF m.vForceSquareBrackets <> 2
lcJSON = lcJSON + ["] + ALLTRIM( LOWER( arrName( liLoop , 1 ) ) ) + ["] + [:]
ENDIF
luValue = EVALUATE( m.vCrsName + [.] + ALLTRIM( arrName( liLoop , 1 ) ) )
* // now add the columvalue depending on its vartype
DO CASE
CASE arrName( liLoop , 2 ) = [C] && Character
lcJSON = lcJSON + ["] + ALLTRIM( luValue ) + ["]
CASE arrName( liLoop , 2 ) = [Y] && Money
lcJSON = lcJSON + CHRTRAN( TRANSFORM( luValue ) . [,] , [.] )
CASE arrName( liLoop , 2 ) = [D] && Date
lcJSON = lcJSON + ["] + DTOC( luValue ) + ["]
CASE arrName( liLoop , 2 ) = [T] && Datetime
lcJSON = lcJSON + ["] + TTOC( luValue , 3 ) + [.000Z] + ["]
CASE arrName( liLoop , 2 ) = [F] && Float
lcJSON = lcJSON + CHRTRAN( TRANSFORM( luValue ) . [,] , [.] )
CASE arrName( liLoop , 2 ) = [I] && Integer
lcJSON = lcJSON + TRANSFORM( luValue )
CASE arrName( liLoop , 2 ) = [L] && Logical
lcJSON = lcJSON + TRANSFORM( IIF( luValue = .T. , [true] , [false] ) )
CASE arrName( liLoop , 2 ) = [N] && Numeric
lcJSON = lcJSON + CHRTRAN( TRANSFORM( luValue ) . [,] , [.] )
ENDCASE
ENDFOR
IF m.vForceSquareBrackets <> 2
lcJSON = lcJSON + [}]
ENDIF
SKIP IN ( m.vCrsName )
ENDDO
* // in case of multiple records, we have to use squarebrackets
* // as each record is a JSON object
IF RECCOUNT( vCrsName ) > 1 OR m.vForceSquareBrackets > 0
lcJSON = "[" + lcJSON + "]"
ENDIF
RETURN lcJSON
ENDFUNC
Wer lieber auf eine fertige Klasse zurückgreifen
möchte wird auf gitHub (vormals CodePlex) fündig:
Die nfJson Programmbibliothek wird kontinuierlich gepflegt und erzeugt
nicht nur JSON sondern kann dieses auch in VFP Cursor wandeln.
Hiermit sind die wichtigsten Vorarbeiten abgeschlossen. Im nächsten Schritt steht nun die Kontaktaufnahme mit einem REST basierenden Webservice an.
Links zu den restlichen Kapiteln:
Einführung / Introduction
Teil 1: Abkürzungen und was sie bedeuten/ Abbreviations and their meaning
Teil 2: Wer ReST sagt, sagt auch JSON
/ In for a ReST, in for a JSON
Teil 3: Leerzeichen und andere Entitäten
/ BLANKS and other entities
Teil 4: JSON und der goldene Konverter
/ JSON and the golden converter
Teil 5: Die Startvorbereitungen
/ preparations for launch
Teil 6: Kleine Denkpause gefällig?
/ in need of a reflection period?
Teil 7: Fertig machen zur Landung
/ preparing for landing
Keine Kommentare:
Kommentar veröffentlichen