├── nfJson ├── nfjsoncreate.PRG ├── performanceTest │ ├── config.fpw │ ├── nfJsonPerfTest.PJT │ ├── nfJsonPerfTest.PJX │ ├── nfjsonperftestSamples.TXT │ └── nfjsonperftest.PRG ├── Tests │ ├── jsonSamples │ │ ├── mapquest.json │ │ ├── mySimpleArray.json │ │ ├── googleMapsDistance.json │ │ ├── dropbox.json │ │ ├── jsonApi.json │ │ ├── iphone photo.json │ │ ├── yahooweather.json │ │ ├── youtubesearch.json │ │ ├── tweeter.json │ │ ├── event.json │ │ ├── weatherService.json │ │ ├── cycloneForecast.json │ │ ├── chutzpahSchema.json │ │ └── googleExtensionSchema.json │ ├── escapetest.PRG │ ├── emptyDateTest.PRG │ ├── how_to_json_to_cursor.prg │ ├── json_to_cursor_test.prg │ ├── how_to_parent_child.prg │ ├── how_to_flatten_object.prg │ ├── examples.PRG │ └── collectionTest.prg ├── nfCursorToJson4vfp.PRG ├── show json in IE.reg ├── nfcursortojson.prg ├── jsonFormat.prg ├── nfcursortoobject.prg ├── nfjsontocursor.PRG ├── nfOpenJson.PRG └── nfjsonread.PRG ├── .gitignore └── README.md /nfJson/nfjsoncreate.PRG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/nfJson/HEAD/nfJson/nfjsoncreate.PRG -------------------------------------------------------------------------------- /nfJson/performanceTest/config.fpw: -------------------------------------------------------------------------------- 1 | screen=off 2 | escape=on 3 | talk=off 4 | develop=off 5 | resource=off 6 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/mapquest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/nfJson/HEAD/nfJson/Tests/jsonSamples/mapquest.json -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/mySimpleArray.json: -------------------------------------------------------------------------------- 1 | [ 2 | [{"name":"aa"},{"name":"bb"}], 3 | [{"name":"cc"},{"name":"dd"}] 4 | ] -------------------------------------------------------------------------------- /nfJson/performanceTest/nfJsonPerfTest.PJT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/nfJson/HEAD/nfJson/performanceTest/nfJsonPerfTest.PJT -------------------------------------------------------------------------------- /nfJson/performanceTest/nfJsonPerfTest.PJX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/nfJson/HEAD/nfJson/performanceTest/nfJsonPerfTest.PJX -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/googleMapsDistance.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/nfJson/HEAD/nfJson/Tests/jsonSamples/googleMapsDistance.json -------------------------------------------------------------------------------- /nfJson/performanceTest/nfjsonperftestSamples.TXT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VFPX/nfJson/HEAD/nfJson/performanceTest/nfjsonperftestSamples.TXT -------------------------------------------------------------------------------- /nfJson/nfCursorToJson4vfp.PRG: -------------------------------------------------------------------------------- 1 | *------------------------------------------------------------------- 2 | * Created by Marco Plaza @nfTools 3 | * ver 1.000 - 20/02/2016 4 | *------------------------------------------------------------------- 5 | 6 | return nfCursorToJson(.f.,.f.,.t.) 7 | -------------------------------------------------------------------------------- /nfJson/show json in IE.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00; 2 | ; Tell IE 7,8,9,10,11 to open JSON documents in the browser on Windows XP and later. 3 | ; 25336920-03F9-11cf-8FD0-00AA00686F13 is the CLSID for the "Browse in place" . 4 | ; 5 | [HKEY_CLASSES_ROOT\MIME\Database\Content Type\application/json] 6 | "CLSID"="{25336920-03F9-11cf-8FD0-00AA00686F13}" 7 | "Encoding"=hex:08,00,00,00 -------------------------------------------------------------------------------- /nfJson/Tests/escapetest.PRG: -------------------------------------------------------------------------------- 1 | clear 2 | 3 | set path to ..\ 4 | 5 | private all 6 | 7 | public pair 8 | 9 | pair = CREATEOBJECT("empty") 10 | 11 | addproperty(pair , [test], [\\"""he\"\\]+chr(13)+chr(20)+chr(22)+["""\///""\"\\"/"ll/\"o/"\"\\\\""\"] ) 12 | 13 | json = nfjsoncreate(pair) 14 | 15 | _cliptext = m.json 16 | ? json 17 | 18 | ee = nfJsonRead(m.json) 19 | 20 | ? 'Test equal?', ee.test == pair.test 21 | 22 | ? ee.test 23 | ? pair.test 24 | -------------------------------------------------------------------------------- /nfJson/nfcursortojson.prg: -------------------------------------------------------------------------------- 1 | *------------------------------------------------------------------- 2 | * Created by Marco Plaza @nfTools 3 | * ver 1.000 - 20/02/2016 4 | *------------------------------------------------------------------- 5 | parameters returnArray,arrayofValues,includestruct,formattedOutput 6 | 7 | LOCAL o 8 | o = nfCursorToObject( m.arrayOfValues,m.includestruct ) 9 | 10 | IF m.returnArray 11 | DIMENSION rows(1) 12 | ACOPY(m.o.rows,'rows') 13 | Return nfJsonCreate(@m.rows,m.formattedOutput,.t.) 14 | ELSE 15 | Return nfJsonCreate(m.o,m.formattedOutput,.t.) 16 | ENDIF 17 | -------------------------------------------------------------------------------- /nfJson/Tests/emptyDateTest.PRG: -------------------------------------------------------------------------------- 1 | 2 | 3 | o = nfJsonRead('{"testDate":"2017-12-01T00:00:00"}') 4 | ? 'should be date:',vartype(o.testDate) 5 | 6 | try 7 | o = nfJsonRead('{"testDate":"2017-25-01T00:00:00"}') 8 | catch to oerr 9 | ? 'should throw error: ',oerr.message 10 | endtry 11 | 12 | o = nfJsonRead('{"testDate":"2017-12-01T01:00:00"}') 13 | ? 'should be time:', vartype(o.testDate) 14 | 15 | o = nfJsonRead('{"testDate":"0000-00-00T00:00:00"}') 16 | ? 'should be empty:',empty(o.testDate), vartype(o.testDate) 17 | 18 | create cursor test ( adatetime t null , adate d null ) 19 | insert into test ( adatetime, adate ) values ( .null.,.null. ) 20 | append blank 21 | 22 | cJson = nfCursorToJson4vfp() 23 | 24 | nfJsonToCursor( m.cJson ) 25 | browse 26 | 27 | close tables ALL 28 | -------------------------------------------------------------------------------- /nfJson/Tests/how_to_json_to_cursor.prg: -------------------------------------------------------------------------------- 1 | 2 | CREATE CURSOR response ( id i,name v(20), secondname v(20) ) 3 | 4 | responseType1 = ' { "persons": [ { "id":1, "name":"John" , "secondName": "Doe" }, { "id":1, "name":"Jane" , "secondName": "Doe" } ] } ' 5 | 6 | 7 | oResponse = nfJsonRead( m.responseType1 ) 8 | 9 | 10 | For Each oRow In oResponse.persons 11 | Insert Into response From Name oRow 12 | Endfor 13 | 14 | 15 | 16 | * from unnamed array: 17 | 18 | responseType2 = '[ { "id":1, "name":"John 2" , "secondName": "Doe 2" }, { "id":1, "name":"Jane 2" , "secondName": "Doe 2" } ] ' 19 | 20 | 21 | oResponse = nfJsonRead( m.responseType2 ) 22 | 23 | * nfjsonRead will always return an object, unnamed arrays get named as "Array" 24 | 25 | For Each oRow In oResponse.array 26 | Insert Into response From Name oRow 27 | Endfor 28 | 29 | BROWSE 30 | -------------------------------------------------------------------------------- /nfJson/Tests/json_to_cursor_test.prg: -------------------------------------------------------------------------------- 1 | *************** 2 | * 3 | * 4 | *************** 5 | 6 | 7 | TEXT to mssample2 noshow 8 | [ 9 | { "CorpCode": "KAKK01", 10 | "MobNo": "9447820950", 11 | "KitID": "2320007005", 12 | "EwireTxnNo": "2" 13 | }, 14 | { "CorpCode": "KAKK01", 15 | "MobNo": "9544727140", 16 | "KitID": "2320007006", 17 | "EwireTxnNo": "2" 18 | } 19 | ] 20 | ENDTEXT 21 | 22 | ox = nfJsonRead(m.mssample2) 23 | 24 | 25 | Create Cursor temp ( corpcode v(10), mobno v(20),kitid v(10), ewireTxnNo v(10) ) 26 | 27 | For Each Row In ox.Array 28 | Insert Into temp From Name Row 29 | Endfor 30 | 31 | BROWSE TITLE 'using nfJsonRead' 32 | 33 | 34 | *** using nfOpenJson: 35 | 36 | TEXT TO curstruc NOSHOW TEXTMERGE PRETEXT 8 37 | - corpcode v(10) $.corpCode 38 | - mobno v(20) $.mobNo 39 | - kitid v(10) $.kitid 40 | - ewireTxnNo v(10) $.ewireTxnNo 41 | ENDTEXT 42 | 43 | nfOpenJson( m.mssample2,'array',m.curstruc) 44 | 45 | BROWSE TITLE 'Using nfOpenJson' 46 | 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | # ========================= 22 | # Operating System Files 23 | # ========================= 24 | 25 | # OSX 26 | # ========================= 27 | 28 | .DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | 32 | # Thumbnails 33 | ._* 34 | 35 | # Files that might appear in the root of a volume 36 | .DocumentRevisions-V100 37 | .fseventsd 38 | .Spotlight-V100 39 | .TemporaryItems 40 | .Trashes 41 | .VolumeIcon.icns 42 | 43 | # Directories potentially created on remote AFP share 44 | .AppleDB 45 | .AppleDesktop 46 | Network Trash Folder 47 | Temporary Items 48 | .apdisk 49 | *.??k 50 | *.fxp 51 | 52 | *.log 53 | 54 | nfJson/Tests/temp/ 55 | 56 | 57 | /nfJson/_customEnv 58 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/dropbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "size": "0 bytes", 3 | "hash": "37eb1ba1849d4b0fb0b28caf7ef3af52", 4 | "bytes": 0, 5 | "thumb_exists": false, 6 | "rev": "714f029684fe", 7 | "modified": "Wed, 27 Apr 2011 22:18:51 +0000", 8 | "path": "/Photos", 9 | "is_dir": true, 10 | "icon": "folder", 11 | "root": "dropbox", 12 | "contents": [ 13 | { 14 | "size": "2.3 MB", 15 | "rev": "38af1b183490", 16 | "thumb_exists": true, 17 | "bytes": 2453963, 18 | "modified": "Mon, 07 Apr 2014 23:13:16 +0000", 19 | "client_mtime": "Thu, 29 Aug 2013 01:12:02 +0000", 20 | "path": "/Photos/flower.jpg", 21 | "photo_info": { 22 | "lat_long": [ 23 | 37.77256666666666, 24 | -122.45934166666667 25 | ], 26 | "time_taken": "Wed, 28 Aug 2013 18:12:02 +0000" 27 | }, 28 | "is_dir": false, 29 | "icon": "page_white_picture", 30 | "root": "dropbox", 31 | "mime_type": "image/jpeg", 32 | "revision": 14511 33 | } 34 | ], 35 | "revision": 29007 36 | } -------------------------------------------------------------------------------- /nfJson/Tests/how_to_parent_child.prg: -------------------------------------------------------------------------------- 1 | *-------------------------------------------- 2 | * using nfJson to create json 3 | * from parent-child cursors: 4 | *-------------------------------------------- 5 | 6 | Clear 7 | Close Data 8 | 9 | Open Database Home()+'samples\northwind\northwind' 10 | 11 | Select Top 2 orderid,customerid,orderdate ; 12 | from orders Order By 1 ; 13 | into Cursor cursample 14 | 15 | * Turn parent cursor to object.rows using nfCursorToObject: 16 | 17 | oOrders = nfcursortoobject() && returns an object with a rows array 18 | 19 | 20 | * get child records for each row: 21 | 22 | For Each oOrder In oOrders.Rows 23 | 24 | Select * From orderdetails ; 25 | where orderid = oorder.orderid ; 26 | into Cursor curdet 27 | 28 | * use nfCursorToObject for child records: 29 | oOrderdetail = nfcursortoobject() 30 | 31 | 32 | * then copy the 'rows' array to parent Object: 33 | AddProperty(oOrder,"orderDetail(1)",Null) 34 | Acopy(oorderdetail.Rows,oorder.orderdetail) 35 | 36 | Endfor 37 | 38 | * we have the object Orders, but 39 | * since you want only the array [], 40 | * we need to pass oOrders.Rows to nfJsonCreate; 41 | * so we have to copy the rows: 42 | 43 | Acopy(oorders.Rows,arows) 44 | 45 | * then just do: 46 | myjson = nfjsoncreate(@m.arows,.T.) 47 | 48 | ? m.myjson 49 | 50 | -------------------------------------------------------------------------------- /nfJson/jsonFormat.prg: -------------------------------------------------------------------------------- 1 | *------------------------------------------------------------- 2 | * Json Format 3 | * Carlos Alloati, 10/06/2019 4 | * VFPX License 5 | *------------------------------------------------------------- 6 | Lparameters pjson 7 | Local c1, c2, cb, instring, jsonfmt, nlen, nlevel, nx 8 | 9 | m.pjson = Alltrim(m.pjson) 10 | m.nlevel = 0 11 | m.instring = .F. 12 | m.jsonfmt = '' 13 | m.c1 = '' 14 | m.c2 = '' 15 | m.nlen = Len(m.pjson) 16 | 17 | For m.nx = 1 To m.nlen 18 | 19 | m.cb = Substr(m.pjson, m.nx, 1) 20 | 21 | If m.nx > 1 Then 22 | m.c1 = Substr(m.pjson, m.nx - 1, 1) 23 | Endif 24 | If m.nx < m.nlen Then 25 | m.c2 = Substr(m.pjson, m.nx + 1, 1) 26 | Endif 27 | If m.cb == '"' And Not m.c1 = '\' 28 | m.instring = Not m.instring 29 | Endif 30 | If m.instring = .F. And m.cb $ '{[' 31 | m.nlevel = m.nlevel + 1 32 | Endif 33 | If m.instring = .F. And m.cb $ '}]' 34 | m.nlevel = m.nlevel - 1 35 | Endif 36 | 37 | Do Case 38 | Case m.instring = .F. And m.cb == '[' And m.c2 == ']' 39 | m.jsonfmt = m.jsonfmt + m.cb 40 | Case m.instring = .F. And m.cb == ']' And m.c1 == '[' 41 | m.jsonfmt = m.jsonfmt + m.cb 42 | Case m.instring = .F. And m.cb $ ',{[' 43 | m.jsonfmt = m.jsonfmt + m.cb + 0h0d0a + Space(m.nlevel * 2) 44 | Case m.instring = .F. And m.cb $ ',}]' 45 | m.jsonfmt = m.jsonfmt + 0h0d0a + Space(m.nlevel * 2) + m.cb 46 | Otherwise 47 | m.jsonfmt = m.jsonfmt + m.cb 48 | Endcase 49 | 50 | Endfor 51 | 52 | Return m.jsonfmt 53 | -------------------------------------------------------------------------------- /nfJson/nfcursortoobject.prg: -------------------------------------------------------------------------------- 1 | *------------------------------------------------------------------- 2 | * Created by Marco Plaza / @nfTools 3 | * ver 1.010 - 07/06/2017 4 | *------------------------------------------------------------------- 5 | parameters copytoarray,includestruct 6 | 7 | private all 8 | 9 | if EMPTY(ALIAS()) 10 | return .f. 11 | endif 12 | 13 | ovfp = createobject('empty') 14 | addproperty(ovfp,'arrayOfValues', m.copytoarray ) 15 | 16 | if copytoarray 17 | 18 | copy to array arows 19 | recordcount = _tally 20 | 21 | if _tally = 0 22 | dimension arows(1) 23 | arows(1) = .null. 24 | endif 25 | 26 | else 27 | 28 | recordcount = 0 29 | count to n 30 | 31 | dimension arows(1) 32 | arows(1) = .null. 33 | 34 | if m.n > 0 35 | 36 | dimension arows(m.n) 37 | recordcount = m.n 38 | 39 | n=0 40 | 41 | scan 42 | n=m.n+1 43 | scatter name ofields memo 44 | arows(n) = m.ofields 45 | endscan 46 | 47 | endif 48 | 49 | endif 50 | 51 | addproperty(ovfp,'recordcount', m.recordCount) 52 | addarray(ovfp,'rows',@m.arows) 53 | 54 | if m.includestruct 55 | 56 | ncols = afields(astruct) 57 | 58 | for n = 1 to ncols 59 | store '' to astruct(n,13),astruct(n,14),astruct(n,15) 60 | store 0 to astruct(n,17),astruct(n,18) 61 | endfor 62 | 63 | addarray(m.ovfp,'aStruct',@m.astruct) 64 | 65 | endif 66 | 67 | RETURN m.oVfp 68 | 69 | *************************************** 70 | Function addArray(o2add2,aName,a2add) 71 | *************************************** 72 | 73 | AddProperty(m.o2add2,(m.aName+'(1)')) 74 | 75 | Acopy(m.a2add,m.o2add2.&aName) -------------------------------------------------------------------------------- /nfJson/nfjsontocursor.PRG: -------------------------------------------------------------------------------- 1 | *------------------------------------------------------------------- 2 | * Created by Marco Plaza / @nfTools 3 | * ver 1.000 - 20/02/2016 4 | * ver 1.100 - 05/08/2017 5 | *------------------------------------------------------------------- 6 | Parameters cJson, cName, forceImportFromArray 7 | 8 | cName = Evl(m.cName,Sys(2015)) 9 | 10 | Private All 11 | 12 | oCursor = nfJsonRead(m.cJson) 13 | 14 | do case 15 | CASE VARTYPE(oCursor) # 'O' 16 | return 17 | case Vartype(oCursor.aStruct) = 'U' 18 | Error 'missing structure data - must create json using nfCursor2Json4vfp() ' 19 | return 20 | case !m.forceImportFromArray and Vartype(oCursor.asarray) = 'L' and oCursor.asarray 21 | Error 'array contains only values; create json using nfCursor2Json4vfp() ; 22 | or use forceImportFromArray only if you are sure your data contains no memo or binary types' 23 | return 24 | endcase 25 | 26 | Create Cursor (m.cName) From Array oCursor.aStruct 27 | 28 | If oCursor.recordCount = 0 29 | Return 30 | Endif 31 | 32 | If oCursor.arrayOfValues 33 | 34 | nFields = Alen(oCursor.aStruct,1) 35 | 36 | i = 'Insert into '+m.cName+' ( ' 37 | i2 = ' values (' 38 | 39 | For N = 1 To m.nFields 40 | 41 | i = m.i+oCursor.aStruct[m.n,1]+',' 42 | 43 | If oCursor.aStruct[m.n,2] = 'D' 44 | wrl = 'ttod(' 45 | wrr = ')' 46 | Else 47 | Store '' To wrl,wrr 48 | Endif 49 | 50 | i2 = m.i2+m.wrl+'oCursor.rows[m.n,'+Transform(m.N)+']'+m.wrr+',' 51 | 52 | Endfor 53 | 54 | i = Left(m.i,Len(m.i)-1)+') '+Left(m.i2,Len(m.i2)-1)+')' 55 | 56 | nRows = oCursor.recordCount 57 | 58 | For N = 1 To m.nRows 59 | &i 60 | Endfor 61 | 62 | Else 63 | 64 | nFields = Alen(oCursor.aStruct,1) 65 | 66 | For Each oRow In oCursor.Rows 67 | Insert Into (cName) From Name oRow 68 | Endfor 69 | 70 | Endif 71 | 72 | Return m.cName 73 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/jsonApi.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [{ 3 | "type": "articles", 4 | "id": "1", 5 | "attributes": { 6 | "title": "JSON API paints my bikeshed!" 7 | }, 8 | "links": { 9 | "self": "http://example.com/articles/1" 10 | }, 11 | "relationships": { 12 | "author": { 13 | "links": { 14 | "self": "http://example.com/articles/1/relationships/author", 15 | "related": "http://example.com/articles/1/author" 16 | }, 17 | "data": { "type": "people", "id": "9" } 18 | }, 19 | "comments": { 20 | "links": { 21 | "self": "http://example.com/articles/1/relationships/comments", 22 | "related": "http://example.com/articles/1/comments" 23 | }, 24 | "data": [ 25 | { "type": "comments", "id": "5" }, 26 | { "type": "comments", "id": "12" } 27 | ] 28 | } 29 | } 30 | }], 31 | "included": [{ 32 | "type": "people", 33 | "id": "9", 34 | "attributes": { 35 | "first-name": "Dan", 36 | "last-name": "Gebhardt", 37 | "twitter": "dgeb" 38 | }, 39 | "links": { 40 | "self": "http://example.com/people/9" 41 | } 42 | }, { 43 | "type": "comments", 44 | "id": "5", 45 | "attributes": { 46 | "body": "First!" 47 | }, 48 | "relationships": { 49 | "author": { 50 | "data": { "type": "people", "id": "2" } 51 | } 52 | }, 53 | "links": { 54 | "self": "http://example.com/comments/5" 55 | } 56 | }, { 57 | "type": "comments", 58 | "id": "12", 59 | "attributes": { 60 | "body": "I like XML better" 61 | }, 62 | "relationships": { 63 | "author": { 64 | "data": { "type": "people", "id": "9" } 65 | } 66 | }, 67 | "links": { 68 | "self": "http://example.com/comments/12" 69 | } 70 | }] 71 | } -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/iphone photo.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu": { 3 | "header": "xProgress SVG Viewer", 4 | "items": [ 5 | { 6 | "id": "Open" 7 | }, 8 | { 9 | "id": "OpenNew", 10 | "label": "Open New" 11 | }, 12 | null, 13 | { 14 | "id": "ZoomIn", 15 | "label": "Zoom In" 16 | }, 17 | { 18 | "id": "ZoomOut", 19 | "label": "Zoom Out" 20 | }, 21 | { 22 | "id": "OriginalView", 23 | "label": "Original View" 24 | }, 25 | null, 26 | { 27 | "id": "Quality" 28 | }, 29 | { 30 | "id": "Pause" 31 | }, 32 | { 33 | "id": "Mute" 34 | }, 35 | null, 36 | { 37 | "id": "Find", 38 | "label": "Find..." 39 | }, 40 | { 41 | "id": "FindAgain", 42 | "label": "Find Again" 43 | }, 44 | { 45 | "id": "Copy" 46 | }, 47 | { 48 | "id": "CopyAgain", 49 | "label": "Copy Again" 50 | }, 51 | { 52 | "id": "CopySVG", 53 | "label": "Copy SVG" 54 | }, 55 | { 56 | "id": "ViewSVG", 57 | "label": "View SVG" 58 | }, 59 | { 60 | "id": "ViewSource", 61 | "label": "View Source" 62 | }, 63 | { 64 | "id": "SaveAs", 65 | "label": "Save As" 66 | }, 67 | null, 68 | { 69 | "id": "Help" 70 | }, 71 | { 72 | "id": "About", 73 | "label": "About xProgress CVG Viewer..." 74 | } 75 | ] 76 | } 77 | } -------------------------------------------------------------------------------- /nfJson/Tests/how_to_flatten_object.prg: -------------------------------------------------------------------------------- 1 | *------------------------------------------------------ 2 | * different methods to flatten json objects to cursor: 3 | *------------------------------------------------------ 4 | close tables all 5 | 6 | 7 | text to mssample2 noshow 8 | [ 9 | { 10 | "Order": { 11 | "Number":"SO43659", 12 | "Date":"2011-05-31T00:00:00" 13 | }, 14 | "AccountNumber":"AW29825", 15 | "Item": { 16 | "Price":136.95, 17 | "Quantity":1 18 | } 19 | }, 20 | { 21 | "Order": { 22 | "Number":"SO43661", 23 | "Date":"2011-06-01T00:00:00" 24 | }, 25 | "AccountNumber":"AW73565", 26 | "Item": { 27 | "Price":866.35, 28 | "Quantity":3 29 | } 30 | } 31 | ] 32 | ENDTEXT 33 | 34 | 35 | *------ 1) using nfJsonRead and existing cursor ( preferred way ) 36 | 37 | oSrc = nfJsonRead(m.mssample2) 38 | 39 | 40 | Create Cursor curStruct2 ( Number v(10),Date d,Customer v(10),itemPrice N(10,2),itemQuantity i, Order m ) 41 | 42 | For Each Row In oSrc.Array 43 | 44 | Insert Into curStruct2 ; 45 | ( Number,Date ,Customer ,itemPrice ,itemQuantity ,Order ) ; 46 | VALUES ; 47 | ( Row.Order.Number, Row.Order.Date, Row.accountNumber, Row.Item.price,Row.Item.quantity, nfJsonCreate(m.row) ) 48 | 49 | Endfor 50 | 51 | Browse Normal Title 'sample 1' 52 | 53 | *------- 2) using gather name ( less code , works even if order or item object have missing keys ) 54 | 55 | Create Cursor curStruct3 ( Number v(10),Date d,accountNumber v(10),price N(10,2),quantity i, sourceDoc m ) 56 | 57 | 58 | For Each oRow In oSrc.Array 59 | 60 | Append Blank 61 | Gather Name oRow 62 | Gather Name oRow.Order 63 | Gather Name oRow.Item 64 | 65 | Replace sourceDoc With nfJsonCreate(m.oRow.Order) 66 | 67 | Endfor 68 | 69 | 70 | Browse Normal Title 'sample 2' 71 | 72 | 73 | * USING NFOPENJSON 74 | 75 | *----- map structure to object: 76 | 77 | text to cStruct pretext 8 noshow 78 | - Number c(10) $.Order.Number 79 | - Date t $.Order.Date 80 | - Customer c(10) $.AccountNumber 81 | - itemPrice n(10,2) $.Item.Price 82 | - itemQuantity i $.Item.Quantity 83 | - Order JSON 84 | endtext 85 | 86 | nfOpenJson( m.mssample2,'$.array',m.cStruct ) 87 | BROWSE TITLE 'sample 3' 88 | 89 | *---------- skipping text-endtext if you like for short structures: 90 | 91 | 92 | nfOpenJson( m.mssample2,'$.array', '; 93 | - Number c(10) $.Order.Number ; 94 | - Date t $.Order.Date ; 95 | - Customer c(10) $.AccountNumber ; 96 | - itemPrice n(10,2) $.Item.Price ; 97 | - itemQuantity i $.Item.Quantity ; 98 | - Order JSON ; 99 | ') 100 | 101 | BROWSE TITLE 'sample 4' 102 | 103 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/yahooweather.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": { 3 | "count": 1, 4 | "created": "2016-02-20T19:34:51Z", 5 | "lang": "es-ES", 6 | "results": { 7 | "channel": { 8 | "title": "Yahoo! Weather - Nome, AK", 9 | "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Nome__AK/*http://weather.yahoo.com/forecast/USAK0170_f.html", 10 | "description": "Yahoo! Weather for Nome, AK", 11 | "language": "en-us", 12 | "lastBuildDate": "Sat, 20 Feb 2016 9:53 am AKST", 13 | "ttl": "60", 14 | "location": { 15 | "city": "Nome", 16 | "country": "United States", 17 | "region": "AK" 18 | }, 19 | "units": { 20 | "distance": "mi", 21 | "pressure": "in", 22 | "speed": "mph", 23 | "temperature": "F" 24 | }, 25 | "wind": { 26 | "chill": "-9", 27 | "direction": "310", 28 | "speed": "5" 29 | }, 30 | "atmosphere": { 31 | "humidity": "60", 32 | "pressure": "29.45", 33 | "rising": "1", 34 | "visibility": "10" 35 | }, 36 | "astronomy": { 37 | "sunrise": "9:41 am", 38 | "sunset": "6:49 pm" 39 | }, 40 | "image": { 41 | "title": "Yahoo! Weather", 42 | "width": "142", 43 | "height": "18", 44 | "link": "http://weather.yahoo.com", 45 | "url": "http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif" 46 | }, 47 | "item": { 48 | "title": "Conditions for Nome, AK at 9:53 am AKST", 49 | "lat": "64.5", 50 | "long": "-165.41", 51 | "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Nome__AK/*http://weather.yahoo.com/forecast/USAK0170_f.html", 52 | "pubDate": "Sat, 20 Feb 2016 9:53 am AKST", 53 | "condition": { 54 | "code": "34", 55 | "date": "Sat, 20 Feb 2016 9:53 am AKST", 56 | "temp": "1", 57 | "text": "Fair" 58 | }, 59 | "description": "\n
\nCurrent Conditions:
\nFair, 1 F
\n
Forecast:
\nSat - Sunny. High: 8 Low: 1
\nSun - AM Snow Showers. High: 20 Low: 11
\nMon - Mostly Cloudy. High: 24 Low: 15
\nTue - Partly Cloudy. High: 23 Low: 20
\nWed - Cloudy. High: 26 Low: 23
\n
\nFull Forecast at Yahoo! Weather

\n(provided by The Weather Channel)
\n", 60 | "forecast": [ 61 | { 62 | "code": "32", 63 | "date": "20 Feb 2016", 64 | "day": "Sat", 65 | "high": "8", 66 | "low": "1", 67 | "text": "Sunny" 68 | }, 69 | { 70 | "code": "14", 71 | "date": "21 Feb 2016", 72 | "day": "Sun", 73 | "high": "20", 74 | "low": "11", 75 | "text": "AM Snow Showers" 76 | }, 77 | { 78 | "code": "28", 79 | "date": "22 Feb 2016", 80 | "day": "Mon", 81 | "high": "24", 82 | "low": "15", 83 | "text": "Mostly Cloudy" 84 | }, 85 | { 86 | "code": "30", 87 | "date": "23 Feb 2016", 88 | "day": "Tue", 89 | "high": "23", 90 | "low": "20", 91 | "text": "Partly Cloudy" 92 | }, 93 | { 94 | "code": "26", 95 | "date": "24 Feb 2016", 96 | "day": "Wed", 97 | "high": "26", 98 | "low": "23", 99 | "text": "Cloudy" 100 | } 101 | ], 102 | "guid": { 103 | "isPermaLink": "false", 104 | "content": "USAK0170_2016_02_24_7_00_AKST" 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /nfJson/performanceTest/nfjsonperftest.PRG: -------------------------------------------------------------------------------- 1 | ************************************************************* 2 | * just for testing purposes. 3 | * you can add your sample json 4 | * strings to nfJsonPerfTest.txt 5 | * 6 | * delimit your json file using 3 Double quotes and a colon : 7 | * 8 | * """ mysample Json : 9 | * 10 | * """ 11 | * 12 | ************************************************************* 13 | 14 | #define millon 1000000 15 | Public oJson, nsamp 16 | 17 | IF _vfp.StartMode # 4 and UPPER(RIGHT(CURDIR(),24)) # upper('\nfJson\performanceTest\') 18 | MESSAGEBOX('Please run from nfJson\performanceTest',0) 19 | RETURN 20 | ENDIF 21 | 22 | SET PATH TO ..\ && nfJson folder 23 | 24 | m.testFile = JUSTPATH(SYS(16,0))+'\nfjsonperftestSamples.TXT' 25 | 26 | IF !FILE(m.testFile) 27 | MESSAGEBOX('Missing '+m.testFile,0) 28 | cJson = [""" Missing Samples File: {"FileName":"nfjsonperftestSamples.TXT"} """] 29 | ELSE 30 | cjson = FILETOSTR(m.testFile) 31 | ENDIF 32 | 33 | Dimension opt(1) 34 | N=1 35 | nsamp = 75 36 | 37 | Do While .T. 38 | op=Strextract(cjson,'"""',':',N) 39 | If Empty(op) 40 | Exit 41 | Endif 42 | Dimension opt(N) 43 | opt(N)=op 44 | N=N+1 45 | Enddo 46 | 47 | o=Createobject('sampletest') 48 | Read Events 49 | 50 | Define Class sampletest As Form 51 | 52 | Height=680 53 | Width=900 54 | ShowWindow=2 55 | Left=300 56 | Top=100 57 | caption='nfJson 1.0000' 58 | 59 | Add Object lblsamplesize As Label With Left=10,Height=25,Width=235,Top=14,fontname='Consolas',FontSize=11,Caption="Number of Samples to be used:" 60 | Add Object txtsamplesize As TextBox With Left=250,Height=25,Width=60,Top=10,fontname='Consolas',FontSize=11,Format="KZ",Inputmask="99999",Controlsource="nsamp" 61 | Add Object samples As ListBox With Anchor=7,Left=10,Top=40,Height = 595,Width = 235,RowSourceType=5,FontName='Consolas',FontSize=10 62 | Add Object json As EditBox With Anchor=15,Left=250,Top=40,Height = 595,Width = 640,RowSourceType=5,FontName='Consolas',FontSize=10 63 | Add Object deCliptext As CommandButton With Anchor=6,Left =9, Top = 640, Height=30, Width=237 , Caption='Json from clipboard',FontName='Consolas',FontSize=11 64 | Add Object TRESULT As TextBox With Anchor=6,Left=250,Height=28,Width=640,Top=641,fontname='Consolas',FontSize=11 65 | 66 | Procedure Init 67 | This.Visible = .T. 68 | This.samples.RowSource='opt' 69 | 70 | Procedure Destroy 71 | Clear Events 72 | 73 | *----------------------------------------- 74 | Procedure deCliptext.Click() 75 | *----------------------------------------- 76 | try 77 | Thisform.procesarJson(_Cliptext) 78 | Catch to oerr 79 | 80 | MessageBox(oerr.message,0) 81 | 82 | EndTry 83 | 84 | *------------------------------------- 85 | Procedure samples.Click() 86 | *------------------------------------- 87 | Thisform.procesarJson( Strextract(cjson,opt(This.ListIndex)+':','"""') ) 88 | 89 | *------------------------------------------ 90 | Function procesarJson(jText) 91 | *------------------------------------------ 92 | 93 | If Vartype(jText) # 'C' 94 | Messagebox('No es Texto!',0) 95 | Return .F. 96 | Endif 97 | 98 | * indicator that results are being recalculated 99 | Thisform.TRESULT.ForeColor = RGB(255,0,0) 100 | 101 | * transform sample json data into vfp Object: 102 | TI=Seconds() 103 | 104 | For nv = 1 To nsamp 105 | oJson = nfJsonRead(jText) 106 | if vartype(ojson) # 'O' 107 | MESSAGEBOX('Test Failed: '+TRANSFORM(oJson),0) 108 | exit 109 | endif 110 | Endfor 111 | 112 | TR=(Seconds()-TI)*millon 113 | 114 | TR = round(TR/nsamp,0) 115 | 116 | * transform vfp object into json string and show result: 117 | TI=Seconds() 118 | 119 | For nv = 1 To nsamp 120 | xJson = nfJsonCreate(oJson,.T.) 121 | endfor 122 | 123 | Thisform.json.Value = xJson 124 | 125 | TR2=(Seconds()-TI)*millon 126 | TR2=round(TR2/nsamp,0) 127 | 128 | Thisform.TRESULT.ForeColor = RGB(0,0,0) 129 | Thisform.TRESULT.Value = ' Read: '+Transform(TR,'99,999')+'us ( '+Transform(millon/TR,'99,999')+' op/sec ) Create: '+Transform(TR2,'99,999')+'us ( '+Transform(millon/TR2,'99,999')+' op/sec )' 130 | 131 | Thisform.json.SelStart=1 132 | 133 | *************************** 134 | Enddefine 135 | *************************** -------------------------------------------------------------------------------- /nfJson/Tests/examples.PRG: -------------------------------------------------------------------------------- 1 | *********************************************** 2 | * nfJsonRead & nfJsonCreate functions test 3 | * Marco Plaza, 2016 4 | ************************************************ 5 | 6 | Close Databases All 7 | Set Talk Off 8 | Set Safety Off 9 | 10 | samplesPath = JUSTPATH(SYS(16,0))+'\jsonSamples\' 11 | tempPath = JUSTPATH(SYS(16,0))+'\temp\' 12 | 13 | Set Path To '..\' additive && nfJson folder additive 14 | 15 | Try 16 | Erase (m.tempPath+'test_*.json') 17 | MD (m.tempPath) 18 | Catch 19 | 20 | Endtry 21 | 22 | Clear 23 | READ 24 | 25 | ************************************************************************* 26 | * will read and parse all json files found in jsonSamples\ 27 | * objects will be members of myObjectFromJson 28 | * inspect later in vfp debugger or simply type "myObjectFromJson." from the command line 29 | ************************************************************************* 30 | 31 | Public myObjectFromJson 32 | 33 | myObjectFromJson = Createobject('empty') 34 | 35 | nFiles = Adir(jsonfiles,m.samplesPath+'*.json') 36 | 37 | ? 'Processing:' 38 | 39 | For x = 1 To m.nFiles 40 | 41 | thisFile = jsonfiles(x,1) 42 | propName = Strtran(Juststem(m.thisFile),' ','') 43 | 44 | ? m.thisFile 45 | 46 | oJson = nfJsonRead( FORCEPATH(m.thisfile,m.samplesPath)) 47 | 48 | AddProperty( myObjectFromJson, m.propName , m.oJson ) 49 | 50 | Endfor 51 | 52 | ****************************************************** 53 | * create json String back from myObjectFromJson 54 | * raw / formatted 55 | ******************************************************* 56 | 57 | jsonRaw = nfJsonCreate( myObjectFromJson ) 58 | jsonFormatted = nfJsonCreate( myObjectFromJson , .T. ) 59 | 60 | 61 | Strtofile( m.jsonFormatted , m.tempPath+'test_Formatted_'+Sys(2015)+'.Json' ) 62 | Strtofile( m.jsonRaw , m.tempPath+'test_Raw_'+Sys(2015)+'.Json' ) 63 | 64 | Modify FILE (m.tempPath+'*.json') Nowait 65 | 66 | =Sys(1500,"_MWI_CASCADE","_MSM_WINDO") 67 | 68 | **************************************************************** 69 | * create vfp objects back from from raw / formatted json 70 | **************************************************************** 71 | 72 | Public oFromRaw,oFromFormatted 73 | Store '' To oFromRaw,oFromFormatted 74 | 75 | oFromRaw = nfJsonRead( m.jsonRaw ) 76 | oFromFormatted = nfJsonRead( m.jsonFormatted ) 77 | 78 | messagebox( 'Please inspect myObjectFromJson , oFromRaw, oFromFormatted in your debugger ',0) 79 | 80 | *************************************************** 81 | * convert cursor to Json using 82 | * different output options: 83 | *************************************************** 84 | 85 | Select Top 2 ; 86 | employeeId,firstName,lastName,birthDate,city ; 87 | from (Sys(2004)+'\samples\northwind\employees') ; 88 | order By 1 Into Cursor temp 89 | 90 | TEXT to demoCursor textmerge 91 | 92 | -Cursor as Array of values: 93 | 94 | << nfCursorToJson(.t.,.t.) >> 95 | 96 | -Cursor as array of objects: 97 | 98 | << nfCursorToJson(.t.) >> 99 | 100 | -Cursor as Object with array of objects ( default) : 101 | 102 | << nfCursorToJson() >> 103 | 104 | -Cursor as Object with array of values: 105 | 106 | << nfCursorToJson(.f.,.t.) >> 107 | 108 | -Cursor as Object with array of objects and original cursor structure: 109 | 110 | << nfCursorToJson(.f.,.f.,.t.) >> 111 | 112 | ENDTEXT 113 | 114 | Strtofile( demoCursor, m.tempPath+'Cursor tests Output.txt') 115 | Modify File m.tempPath+'Cursor tests Output.txt' nowait 116 | 117 | **************************************************************** 118 | * create a cursor back from json: 119 | * json must be created using nfCursortoJson( .f.,.f.,.t. ) 120 | * a wrapper function is included for this purpose: 121 | **************************************************************** 122 | 123 | Select * ; 124 | from (Sys(2004)+'\samples\northwind\employees') ; 125 | order By 1 Into Cursor temp 126 | 127 | cJson = nfCursorToJson4vfp() 128 | 129 | close databases 130 | 131 | * create named cursor: 132 | 133 | nfJsonToCursor( m.cJson , 'imaCursorFromJson' ) 134 | 135 | go top 136 | browse nowait 137 | messagebox( 'Cursor back from Json!',64) 138 | 139 | * creates a temp cursor, return alias name: 140 | 141 | * myTempCursor = nfJsonToCursor( m.cJson ) 142 | -------------------------------------------------------------------------------- /nfJson/Tests/collectionTest.prg: -------------------------------------------------------------------------------- 1 | ********************************************** 2 | * collections to json and back to vfp 3 | ********************************************** 4 | 5 | Close Databases All 6 | Set Talk Off 7 | Set Safety Off 8 | 9 | IF UPPER(RIGHT(CURDIR(),14)) # upper('\nfJson\tests\') 10 | MESSAGEBOX('Please run from nfJson\tests',0) 11 | RETURN 12 | ENDIF 13 | 14 | Set Path To ..\ && nfJson folder additive 15 | 16 | Try 17 | Erase temp\test_*.json 18 | MD temp 19 | Catch 20 | 21 | Endtry 22 | 23 | Clear 24 | 25 | oparent = CREATEOBJECT('empty') 26 | 27 | PUBLIC oCollection 28 | 29 | ADDPROPERTY(oParent,'collectionTest1',Createobject("collection")) 30 | 31 | oCollection = Createobject("collection") 32 | addcollection( oParent.collectionTest1 ) 33 | addcollection( oCollection ) 34 | 35 | Clear 36 | 37 | Erase temp\colltest.json 38 | 39 | ****** CONVERT COLLECTION TO JSON AND SHOW FILE: 40 | 41 | Set Path To ..\ 42 | 43 | declare arrayTest(2) 44 | 45 | * test 1: object with collection and collection as array items 46 | arrayTest(1) = m.oParent 47 | arrayTest(2) = m.oCollection 48 | cJson1 = nfJsonCreate(@arrayTest,.T.,.T.) 49 | 50 | * test 2 51 | * myforms is the name we want to assign to the root element ( the collection ) of resulting object 52 | cJson2 = nfJsonCreate(oCollection,.T.,.T.,'myForms') 53 | 54 | Strtofile( m.cJson2 ,'temp\colltest.json') 55 | 56 | Modify File temp\colltest.json 57 | 58 | && reset the variable, open debugger may not show object changes if you don't do so. 59 | 60 | PUBLIC oCollectionFromJson1 61 | PUBLIC oDebugCollection1 62 | 63 | oCollectionFromJson1 = '' 64 | oDebugCollection1 = '' 65 | 66 | oCollectionFromJson1 = nfJsonRead(m.cJson1,.T.) && <- this will parse json and create a vfp collection 67 | oDebugCollection1 = nfJsonRead(m.cJson1) && <- this will parse object as it is represented in json 68 | 69 | PUBLIC oCollectionFromJson2 70 | PUBLIC oDebugCollection2 71 | 72 | oCollectionFromJson2 = '' 73 | oDebugCollection2 = '' 74 | 75 | oCollectionFromJson2 = nfJsonRead(m.cJson2,.T.) && <- this will parse json and create a vfp collection 76 | oDebugCollection2 = nfJsonRead(m.cJson2,.F.) && <- this will parse object as it is represented in json 77 | 78 | *** test: 79 | * note: revived items of keyless collections may not appear on the original position 80 | * this is a problem not only for nfJson but any array to object serialization 81 | * check: 82 | * 83 | * https://www.firebase.com/docs/rest/guide/understanding-data.html 84 | * https://www.firebase.com/blog/2014-04-28-best-practices-arrays-in-firebase.html 85 | * 86 | * rule: avoid keyless collections if you need to hard code element positions or depend on them somehow 87 | * 88 | * test 1: 89 | * messagebox( [ oCollectionFromJson.array(2).Item(5).item(3).item(4).item('Product') = ]+ oCollectionFromJson.array(1).Item(5).item(3).item(4).item('Product'),0) 90 | messagebox( [ oCollectionFromJson1.array(1).collectiontest1.Item(5).item(3).item(4).item('Product') = ]+ oCollectionFromJson1.array(1).collectionTest1.Item(5).item(3).item(4).item('Product'),0,'Collection from Json created using nfJsonCreate') 91 | messagebox( [ oCollectionFromJson1.array(2).Item(5).item(3).item(4).item('Product') = ]+ oCollectionFromJson1.array(2).Item(5).item(3).item(4).item('Product'),0,'Collection from Json created using nfJsonCreate') 92 | 93 | * test 2: ( using root name for collection object ) 94 | messagebox( [ oCollectionFromJson2.myforms.Item(5).item(3).item(4).item('Product') = ]+ oCollectionFromJson2.myforms.Item(5).item(3).item(4).item('Product'),0,'test 2: ( using root name "myForms" for collection object at nfJsonCreate )') 95 | 96 | 97 | *--------------------------------------- 98 | FUNCTION addcollection( oo ) 99 | *--------------------------------------- 100 | WITH m.oo 101 | 102 | .Add(123) && item 1 103 | .Add("AAA") && item 2 104 | .Add(createobject("empty")) && item 3 105 | .Add(Createobject("session")) && item 4 106 | .Add(Createobject("collection")) && item 5 107 | 108 | With .Item(5) 109 | 110 | .Add(456) && item 1 111 | .Add("BBB") && item 2 112 | .Add(Createobject("collection")) && item 3 113 | 114 | With .Item(3) 115 | 116 | .Add(789) && item 1 117 | .Add("CCC") && item 2 118 | .Add(Createobject("session")) && item 3 119 | .Add(Createobject("Collection")) && item 4 120 | 121 | with .Item(4) 122 | .Add('VFP','Product') 123 | .Add(Program(),'Executing program Name') 124 | endwith 125 | 126 | Endwith 127 | 128 | .Add(Createobject("container")) && item 5 129 | 130 | Endwith 131 | 132 | Endwith 133 | -------------------------------------------------------------------------------- /nfJson/nfOpenJson.PRG: -------------------------------------------------------------------------------- 1 | *--------------------------------------------- 2 | * Marco Plaza, 2017 @nfTools 3 | * usage sample see json_to_cursor_test.prg 4 | * 5 | * refer to https://docs.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql 6 | * 7 | * ver 0.91 2019/04/03 - 8 | * 9 | *--------------------------------------------- 10 | Parameters cjson, carraypath, cstruct 11 | 12 | carraypath = Evl(m.carraypath,'.array') 13 | carraypath = '.'+Ltrim(m.carraypath,1,'.','$') 14 | 15 | Private All 16 | 17 | Try 18 | 19 | oj = '' 20 | 21 | Do Case 22 | Case Pcount() = 1 23 | cres = json2kvcursor( m.cjson ) 24 | Case Pcount() = 3 25 | cres = json2kvcursor( m.cjson, m.carraypath ) 26 | Otherwise 27 | Error 'missing parameter' 28 | Endcase 29 | 30 | 31 | =Alines( cc, cstruct,7,'-') 32 | 33 | isql = ' SELECT ' 34 | 35 | For Each Line In cc 36 | 37 | oproperty = Strextract(M.line,[$.],[ ],1,2) 38 | castexp = Getwordnum(M.line,2,' ') 39 | fieldalias = Getwordnum(M.line,1,' ') 40 | 41 | cexp = Textmerge("ncast('<< evl(m.oProperty,m.fieldAlias) >> as <>')") 42 | 43 | If m.castexp = 'JSON' 44 | m.cexp = 'cast( '+m.cexp+' as m )' 45 | Endif 46 | 47 | m.cexp = m.cexp + ' as '+ m.fieldalias 48 | 49 | isql = isql+' '+m.cexp+',' 50 | 51 | Endfor 52 | 53 | isql = Rtrim(M.isql,1,',')+' from (cres) where vtooj() into cursor result' 54 | 55 | &isql 56 | 57 | Catch To oerror 58 | 59 | Endtry 60 | 61 | If Vartype(m.oerror) = 'O' 62 | Error oerror.Message 63 | Endif 64 | 65 | *------------------------------- 66 | Function vtooj() 67 | *------------------------------- 68 | oj = nfjsonread( Value ) 69 | 70 | *------------------------------------ 71 | Function ncast( Path ) 72 | *------------------------------------- 73 | If 'as json' $ Lower(m.path) 74 | 75 | vname = 'oj.'+Getwordnum(m.path,1) 76 | 77 | If Type(m.vname,1) = 'A' 78 | ic = 'acopy('+m.vname+',tarray)' 79 | &ic 80 | Return nfjsoncreate(@tarray) 81 | Else 82 | Return nfjsoncreate(Eval(m.vname)) 83 | Endif 84 | 85 | Else 86 | 87 | Return Evaluate(' cast( oj.'+m.path+')') 88 | 89 | Endif 90 | 91 | *------------------------------------------------------ 92 | Function json2kvcursor(cjsonstr,cpropertypath) 93 | *------------------------------------------------------ 94 | 95 | kk = nfjsonread(m.cjsonstr) 96 | 97 | If Isnull(m.kk) 98 | Return .Null. 99 | Endif 100 | 101 | Try 102 | If Pcount() > 1 103 | 104 | cpropertypath = Ltrim(m.cpropertypath,1,'$') 105 | pname = Substr(m.cpropertypath,Rat('.',m.cpropertypath)+1) 106 | 107 | If Type('m.kk'+m.cpropertypath,1) = 'A' 108 | Acopy(m.kk&cpropertypath,lretval) 109 | Else 110 | lretval = Evaluate('kk'+m.cpropertypath) 111 | Endif 112 | Else 113 | lretval = kk 114 | Endif 115 | Catch 116 | lretval = .Null. 117 | Endtry 118 | 119 | Do Case 120 | 121 | Case Isnull(m.lretval) 122 | Return .Null. 123 | 124 | Otherwise 125 | 126 | cn = Sys(2015) 127 | 128 | Create Cursor ( m.cn ) ( Key c(40), Value m Null, Type c(1) ) 129 | Do Case 130 | Case Type('m.lretval',1) = 'A' 131 | arraytokvtable( @m.lretval ) 132 | Case Vartype(m.lretval) # 'O' 133 | tr = Createobject('empty') 134 | AddProperty(m.tr,m.pname,m.lretval) 135 | objecttokvtable( m.tr ) 136 | Otherwise 137 | objecttokvtable( m.lretval ) 138 | Endcase 139 | 140 | Return m.cn 141 | 142 | Endcase 143 | 144 | *--------------------------------------------- 145 | Function arraytokvtable( aa ) 146 | *--------------------------------------------- 147 | 148 | nitem = 1 149 | For Each thisval In aa 150 | 151 | If Vartype(m.thisval) = 'O' 152 | thisval = nfjsoncreate( m.thisval) 153 | Endif 154 | 155 | Insert Into (m.cn) ; 156 | ( Key, Value , Type ) ; 157 | values ; 158 | ( Transform(nitem) ,Iif(Isnull(m.thisval),.Null.,Cast( m.thisval As m ) ) , Vartype( m.thisval ) ) 159 | nitem = m.nitem+1 160 | Endfor 161 | 162 | *-------------------------------------------------- 163 | Function objecttokvtable( ox ) 164 | *-------------------------------------------------- 165 | 166 | Amembers( op, m.ox ) 167 | 168 | For Each pname In op 169 | 170 | If Type('m.oX.&pName',1) = 'A' 171 | 172 | Dimension acc(1) 173 | 174 | Acopy(m.ox.&pname,acc) 175 | thisval = nfjsoncreate( @m.acc ) 176 | 177 | Else 178 | 179 | thisval = m.ox.&pname 180 | 181 | If Vartype(m.thisval) = 'O' 182 | thisval = nfjsoncreate( m.thisval) 183 | Endif 184 | 185 | Endif 186 | 187 | Insert Into (m.cn) ; 188 | ( Key, Value , Type ) ; 189 | values ; 190 | ( m.pname ,Iif(Isnull(m.thisval),.Null.,Cast( m.thisval As m ) ) , Vartype( m.thisval ) ) 191 | 192 | Endfor 193 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/youtubesearch.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "youtube#searchListResponse", 3 | "etag": "\"DsOZ7qVJA4mxdTxZeNzis6uE6ck/VuNRg62vHNxW6mcRbxVMeVTsda8\"", 4 | "regionCode": "VE", 5 | "pageInfo": { 6 | "totalResults": 5, 7 | "resultsPerPage": 5 8 | }, 9 | "items": [ 10 | { 11 | "kind": "youtube#searchResult", 12 | "etag": "\"DsOZ7qVJA4mxdTxZeNzis6uE6ck/HP8VuD8fxeF39vskUYPKedXuiQM\"", 13 | "id": { 14 | "kind": "youtube#video", 15 | "videoId": "F_ikTQ0kRFI" 16 | }, 17 | "snippet": { 18 | "publishedAt": "2014-03-19T03:04:11.000Z", 19 | "channelId": "UCQxJseaR37j0hd8cNx4BbNg", 20 | "title": "IntellisenseX, Jim Nelson: OFUG, VFP, Foxpro", 21 | "description": "", 22 | "thumbnails": { 23 | "default": { 24 | "url": "https://i.ytimg.com/vi/F_ikTQ0kRFI/default.jpg", 25 | "width": 120, 26 | "height": 90 27 | }, 28 | "medium": { 29 | "url": "https://i.ytimg.com/vi/F_ikTQ0kRFI/mqdefault.jpg", 30 | "width": 320, 31 | "height": 180 32 | }, 33 | "high": { 34 | "url": "https://i.ytimg.com/vi/F_ikTQ0kRFI/hqdefault.jpg", 35 | "width": 480, 36 | "height": 360 37 | } 38 | }, 39 | "channelTitle": "", 40 | "liveBroadcastContent": "none" 41 | } 42 | }, 43 | { 44 | "kind": "youtube#searchResult", 45 | "etag": "\"DsOZ7qVJA4mxdTxZeNzis6uE6ck/nYC3bzyNP_SX5JtKiq-T4VMd4Qs\"", 46 | "id": { 47 | "kind": "youtube#video", 48 | "videoId": "BBC3Ly6Im10" 49 | }, 50 | "snippet": { 51 | "publishedAt": "2014-04-16T03:31:44.000Z", 52 | "channelId": "UCQxJseaR37j0hd8cNx4BbNg", 53 | "title": "No-Fox: OFUG,VFP,Foxpro", 54 | "description": "No-Fox presentation by Marco Plaza.", 55 | "thumbnails": { 56 | "default": { 57 | "url": "https://i.ytimg.com/vi/BBC3Ly6Im10/default.jpg", 58 | "width": 120, 59 | "height": 90 60 | }, 61 | "medium": { 62 | "url": "https://i.ytimg.com/vi/BBC3Ly6Im10/mqdefault.jpg", 63 | "width": 320, 64 | "height": 180 65 | }, 66 | "high": { 67 | "url": "https://i.ytimg.com/vi/BBC3Ly6Im10/hqdefault.jpg", 68 | "width": 480, 69 | "height": 360 70 | } 71 | }, 72 | "channelTitle": "", 73 | "liveBroadcastContent": "none" 74 | } 75 | }, 76 | { 77 | "kind": "youtube#searchResult", 78 | "etag": "\"DsOZ7qVJA4mxdTxZeNzis6uE6ck/-Z2Z8_2qCeATuZUAsSWsh_2HBbw\"", 79 | "id": { 80 | "kind": "youtube#video", 81 | "videoId": "GwEnkajNC_g" 82 | }, 83 | "snippet": { 84 | "publishedAt": "2014-01-22T04:02:24.000Z", 85 | "channelId": "UCQxJseaR37j0hd8cNx4BbNg", 86 | "title": "VFPX Round Up, Rick Schummer: OFUG, Visual Foxpro", 87 | "description": "Rich Schummer presents highlights of VFPx.", 88 | "thumbnails": { 89 | "default": { 90 | "url": "https://i.ytimg.com/vi/GwEnkajNC_g/default.jpg", 91 | "width": 120, 92 | "height": 90 93 | }, 94 | "medium": { 95 | "url": "https://i.ytimg.com/vi/GwEnkajNC_g/mqdefault.jpg", 96 | "width": 320, 97 | "height": 180 98 | }, 99 | "high": { 100 | "url": "https://i.ytimg.com/vi/GwEnkajNC_g/hqdefault.jpg", 101 | "width": 480, 102 | "height": 360 103 | } 104 | }, 105 | "channelTitle": "", 106 | "liveBroadcastContent": "none" 107 | } 108 | }, 109 | { 110 | "kind": "youtube#searchResult", 111 | "etag": "\"DsOZ7qVJA4mxdTxZeNzis6uE6ck/wJGMkd9a_Cvmx1wBEsLi1JwLIO4\"", 112 | "id": { 113 | "kind": "youtube#video", 114 | "videoId": "5zXi5aOUgcw" 115 | }, 116 | "snippet": { 117 | "publishedAt": "2014-02-19T03:42:42.000Z", 118 | "channelId": "UCQxJseaR37j0hd8cNx4BbNg", 119 | "title": "Customer Service - OFUG, VFP", 120 | "description": "Customer Service, presentation by Jody Meyer. OFUG, Visual Foxpro.", 121 | "thumbnails": { 122 | "default": { 123 | "url": "https://i.ytimg.com/vi/5zXi5aOUgcw/default.jpg", 124 | "width": 120, 125 | "height": 90 126 | }, 127 | "medium": { 128 | "url": "https://i.ytimg.com/vi/5zXi5aOUgcw/mqdefault.jpg", 129 | "width": 320, 130 | "height": 180 131 | }, 132 | "high": { 133 | "url": "https://i.ytimg.com/vi/5zXi5aOUgcw/hqdefault.jpg", 134 | "width": 480, 135 | "height": 360 136 | } 137 | }, 138 | "channelTitle": "", 139 | "liveBroadcastContent": "none" 140 | } 141 | }, 142 | { 143 | "kind": "youtube#searchResult", 144 | "etag": "\"DsOZ7qVJA4mxdTxZeNzis6uE6ck/OGTN3IgI8M2LcGKCX8R_AgKcBJE\"", 145 | "id": { 146 | "kind": "youtube#video", 147 | "videoId": "czQhb9W1ckA" 148 | }, 149 | "snippet": { 150 | "publishedAt": "2014-05-02T01:48:17.000Z", 151 | "channelId": "UCQxJseaR37j0hd8cNx4BbNg", 152 | "title": "Grouping in Thor's Size/Position", 153 | "description": "A grouping enhancement I added to Thor's Size/Position tool.", 154 | "thumbnails": { 155 | "default": { 156 | "url": "https://i.ytimg.com/vi/czQhb9W1ckA/default.jpg", 157 | "width": 120, 158 | "height": 90 159 | }, 160 | "medium": { 161 | "url": "https://i.ytimg.com/vi/czQhb9W1ckA/mqdefault.jpg", 162 | "width": 320, 163 | "height": 180 164 | }, 165 | "high": { 166 | "url": "https://i.ytimg.com/vi/czQhb9W1ckA/hqdefault.jpg", 167 | "width": 480, 168 | "height": 360 169 | } 170 | }, 171 | "channelTitle": "", 172 | "liveBroadcastContent": "none" 173 | } 174 | } 175 | ] 176 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nfJson 2 | 3 | Project developer: Marco Plaza [GitHub/nfoxdev](https://github.com/nfoxdev) 4 | 5 | [Issues?](https://github.com/VFPX/nfJson/issues) - [Discussions/Ideas](https://github.com/VFPX/nfJson/discussions) 6 | 7 | **A set of fast performance, reliable and easy to use Json functions using pure VFP.** 8 | 9 | ## Functions & Usage 10 | 11 | ( Each function is a single prg - No additional dependencies / Requires VFP9 ) 12 | 13 | * oJson = **nfJsonRead(**cJsonString , _lReviveCollections_**)** 14 | Example : 15 | jsonstr = '{"name":"John", "age":30, "family":{"wife":"Susana","son":"Tom"}, "location":"texas"}' 16 | vfpobj = nfJsonRead(jsonstr) 17 | ? vfpobj.age && 30 18 | ? vfpobj.family.son && Tom 19 | 20 | *lReviveCollections: nfJsoncreate stringify key/keyless kcollections as arrays; set this flag if you are parsing json created with nfjsoncreate that 21 | you know have a vfp collection; this will perform a extra step to get your collections back from the array representation ( revive it ) or set it to 22 | false to view your collections as arrays for debugging purposes -check collectiontest.prg in test folder. 23 | 24 | * cJsonString = **nfJsonCreate(**oVfp, _lFormattedOutput, lNoNullArrayItems,cRootName,aMembersFlag_**)** 25 | 26 | * **nfJsonToCursor(**cJson, _cCursorName , lForceImportFromArray_**)** ( creates cursor back from Json created using nfCursorToJson4vfp, 27 | for any other case see nfOpenJson and notes below: ) 28 | 29 | * cJsonString = **nfCursorToJson4vfp()** _&& converts current open table/cursor to Json suitable for later use of nfJsonToCursor()_ 30 | 31 | * cJsonString = **nfCursorToJson(**_lReturnArray, lArrayofValues, lIncludestruct, lFormattedOutput_**)** converts current open table/cursor to Json 32 | 33 | * oJson = **nfCursorToObject(**_lCopyToArray, lIncludeStruct_**)** 34 | 35 | * cJsonString = **jsonFormat( cJsonStr )\*** Format json string w/o validate or change element positions 36 | 37 | * nfOpenJson(** cJsonString , [ cArrayPath ], [cCursorStructure & object mappings ] ) 38 | 39 | Similar to SqlServer 2016 openJson function 40 | 41 | To convert json to cursor: 42 | 43 | For simple 1:1 conversion I recommend to 44 | simply use nfJsonRead and a existing 45 | destination cursor like this: 46 | 47 | ``` 48 | TEXT to mssample2 noshow 49 | [ 50 | { "CorpCode": "KAKK01", 51 | "MobNo": "9447820950", 52 | "KitID": "2320007005", 53 | "EwireTxnNo": "2" 54 | }, 55 | { "CorpCode": "KAKK01", 56 | "MobNo": "9544727140", 57 | "KitID": "2320007006", 58 | "EwireTxnNo": "2" 59 | } 60 | ] 61 | ENDTEXT 62 | 63 | ox = nfJsonRead(m.mssample2) 64 | Create Cursor temp ( corpcode v(10), mobno v(20),kitid v(10), ewireTxnNo v(10) ) 65 | 66 | For Each Row In ox.Array 67 | Insert Into temp From Name Row 68 | Endfor 69 | 70 | BROWSE TITLE 'using nfJsonRead' 71 | 72 | ``` 73 | 74 | 75 | ** Use nfOpenJson to flatten objects 76 | with ease ( check a discussion here https://www.tek-tips.com/viewthread.cfm?qid=1796615 ) 77 | 78 | ``` 79 | *** using nfOpenJson: 80 | 81 | TEXT to mssample3 noshow 82 | [ 83 | { "CorpCode": "KAKK01", 84 | "MobNo": "9447820950", 85 | "KitID": "2320007005", 86 | "EwireTxnNo": "2", 87 | "person":{"name":"Curt","age":54,"phone":["12345","5645466"]} 88 | }, 89 | { "CorpCode": "KAKK01", 90 | "MobNo": "9544727140", 91 | "KitID": "2320007006", 92 | "EwireTxnNo": "2", 93 | "person":{"name":"David","age":55,"phone":["142145","4665465"]} 94 | } 95 | ] 96 | ENDTEXT 97 | 98 | TEXT TO curstruc NOSHOW TEXTMERGE PRETEXT 8 99 | - corpcode v(10) $.corpCode 100 | - mobno v(20) $.mobNo 101 | - kitid v(10) $.kitid 102 | - ewireTxnNo v(10) $.ewireTxnNo 103 | - name v(10) $.person.Name 104 | - age i $.person.age 105 | - phone1 v(10) $.person.phone[1] 106 | - phone2 v(10) $.person.phone[2] 107 | ENDTEXT 108 | 109 | nfOpenJson( m.mssample3,'array',m.curstruc) 110 | 111 | BROWSE TITLE 'Using nfOpenJson' 112 | ``` 113 | 114 | 115 | ## Tests & Sample files 116 | 117 | * **nfOpenJsonTest:** samples taken from [https://docs.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql](https://docs.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql) 118 | 119 | * **nfJsonPerfTest.prg:** just run it and choose one of the embedded json samples from the list to check performance on your pc. ( Allows you to parse Json from clipboard too. ) 120 | 121 | * **examples.prg** will parse the next Json files included in JsonSamples folder. just run from test folder and see sample code and output. 122 | 123 | * **collectionTest.prg:** creates a complex collection , converts it to Json and back to vfp. 124 | 125 | * Sample Json files included: 126 | * youtubesearch.json 127 | * cycloneForecast.json 128 | * dropbox.json 129 | * googleMapsDistance.json 130 | * iphone photo.json 131 | * mapquest.json 132 | * mySimpleArray.json 133 | * tweeter.json 134 | * weatherService.json 135 | * yahooweather.json 136 | 137 | ## Release Notes 138 | 139 | 2022/07/09 ( PatrickvonDeetzen ) 140 | * create json with special characters is now significantly faster (changes made in function "escapeandencode") 141 | * updated performance test & added new test sample 142 | * bug fix: missing m. in nfcursortoobject.prg 143 | 144 | 2019/06/14 145 | 146 | * JsonFormat function by Carlos Alloati 147 | 148 | 2017/08/05 149 | 150 | * no matter wich strictdate setting you have set, a JsonDateTime "0000-00-00T00:00:00" will return an empty date. 151 | * valid JsonDates with time "T00:00:00" will return a date value ( ie: {"testDate":"2017-12-01T00:00:00"} ) 152 | * invalid dates ( ie 2017/50/50 ) properly formatted as Json Date ( ie: 2017-50-50T00:00:00 ) will throw error; 153 | ( previous behavior was to return .null. ) 154 | 155 | 2017/03/10 156 | 157 | * fixed: proper support for 19 character strings with ISO basic date format & different strictdate settings. 158 | 159 | 2017/02/05 160 | 161 | * fixed: nfJsonRead bug fix: incorrect parsing for strings terminated with escaped double quotes; minor changes & code refactoring. 162 | 163 | 2017/01/11 164 | 165 | * fixed: nfJsonRead: incorrect unescaped output with "set exact = on" 166 | * escapetest.prg - removed "leftover" lines. 167 | 168 | 2017/01/11 169 | 170 | * fixed issue escaping values terminated with " 171 | * nfJsonRead: removed parameter "isFile" now you can just pass a file name or string 172 | * added test: escapeTest.prg 173 | 174 | 2016/09/28 175 | 176 | * minor bug fix: zero item collections created as 1 empty item collection 177 | * proper indent for raw/formatted collection objects 178 | 179 | 2016/08/16 180 | 181 | * nfJsonPerfTest: added compiled exe, samples file ships as a separate file for you to edit 182 | * fixed bug on test prgs: clean installs would fail due to missing temp folder on distribution zip w/o tests\temp folder 183 | * fixed bug on collectionTest 184 | 185 | 2016/07/22 186 | 187 | * nfJsonRead: Improved error management 188 | * CollectionTest: added new test 189 | 190 | 2016/07/20 191 | 192 | * Fixed bug: missing closing curly brace on collections as object member 193 | * Updated collections program test 194 | 195 | 2016/07/09 196 | 197 | * Automatic cast for datetime properties ( ISO-8601 basic format & vfp compilant as described on [https://en.wikipedia.org/wiki/ISO_8601#Times](https://en.wikipedia.org/wiki/ISO_8601#Times). ) 198 | * nfJsonToCursor Bug Fix: "Date/datetime evaluated to an invalid value" while running under "strictdate = 1" converting empty dates back from Json 199 | 200 | 2016/07/04 201 | 202 | * Added support for control characters encoding ( chr( 0) ~ chr(31) ) 203 | 204 | 2016/05/05 205 | 206 | * invalid Json error shows calling program information 207 | 208 | 2016/04/02 209 | 210 | * complex nested objects/arrays validation 211 | * missing object/array closures validation 212 | 213 | 2016/03/28 214 | 215 | * nfJsonRead performs JSON validation: invalid Json throws error indicating reason. 216 | * nfJsonPerfTest: proper error management enabled for invalid Json input from clipboard 217 | * nfJsonToCursor: use of strict date format 218 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/tweeter.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "coordinates": null, 3 | "truncated": false, 4 | "created_at": "Tue Aug 28 21:16:23 +0000 2012", 5 | "favorited": false, 6 | "id_str": "240558470661799936", 7 | "in_reply_to_user_id_str": null, 8 | "entities": { 9 | "urls": [ 10 | 11 | ], 12 | "hashtags": [ 13 | 14 | ], 15 | "user_mentions": [ 16 | 17 | ] 18 | }, 19 | "text": "just another test", 20 | "contributors": null, 21 | "id": 240558470661799936, 22 | "retweet_count": 0, 23 | "in_reply_to_status_id_str": null, 24 | "geo": null, 25 | "retweeted": false, 26 | "in_reply_to_user_id": null, 27 | "place": null, 28 | "source": "OAuth Dancer Reborn", 29 | "user": { 30 | "name": "OAuth Dancer", 31 | "profile_sidebar_fill_color": "DDEEF6", 32 | "profile_background_tile": true, 33 | "profile_sidebar_border_color": "C0DEED", 34 | "profile_image_url": "http://a0.twimg.com/profile_images/730275945/oauth-dancer_normal.jpg", 35 | "created_at": "Wed Mar 03 19:37:35 +0000 2010", 36 | "location": "San Francisco, CA", 37 | "follow_request_sent": false, 38 | "id_str": "119476949", 39 | "is_translator": false, 40 | "profile_link_color": "0084B4", 41 | "entities": { 42 | "url": { 43 | "urls": [{ 44 | "expanded_url": null, 45 | "url": "http://bit.ly/oauth-dancer", 46 | "indices": [ 47 | 0, 48 | 26 49 | ], 50 | "display_url": null 51 | }] 52 | }, 53 | "description": null 54 | }, 55 | "default_profile": false, 56 | "url": "http://bit.ly/oauth-dancer", 57 | "contributors_enabled": false, 58 | "favourites_count": 7, 59 | "utc_offset": null, 60 | "profile_image_url_https": "https://si0.twimg.com/profile_images/730275945/oauth-dancer_normal.jpg", 61 | "id": 119476949, 62 | "listed_count": 1, 63 | "profile_use_background_image": true, 64 | "profile_text_color": "333333", 65 | "followers_count": 28, 66 | "lang": "en", 67 | "protected": false, 68 | "geo_enabled": true, 69 | "notifications": false, 70 | "description": "", 71 | "profile_background_color": "C0DEED", 72 | "verified": false, 73 | "time_zone": null, 74 | "profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/80151733/oauth-dance.png", 75 | "statuses_count": 166, 76 | "profile_background_image_url": "http://a0.twimg.com/profile_background_images/80151733/oauth-dance.png", 77 | "default_profile_image": false, 78 | "friends_count": 14, 79 | "following": false, 80 | "show_all_inline_media": false, 81 | "screen_name": "oauth_dancer" 82 | }, 83 | "in_reply_to_screen_name": null, 84 | "in_reply_to_status_id": null 85 | }, { 86 | "coordinates": { 87 | "coordinates": [-122.25831, 88 | 37.871609 89 | ], 90 | "type": "Point" 91 | }, 92 | "truncated": false, 93 | "created_at": "Tue Aug 28 21:08:15 +0000 2012", 94 | "favorited": false, 95 | "id_str": "240556426106372096", 96 | "in_reply_to_user_id_str": null, 97 | "entities": { 98 | "urls": [{ 99 | "expanded_url": "http://blogs.ischool.berkeley.edu/i290-abdt-s12/", 100 | "url": "http://t.co/bfj7zkDJ", 101 | "indices": [ 102 | 79, 103 | 99 104 | ], 105 | "display_url": "blogs.ischool.berkeley.edu/i290-abdt-s12/" 106 | }], 107 | "hashtags": [ 108 | 109 | ], 110 | "user_mentions": [{ 111 | "name": "Cal", 112 | "id_str": "17445752", 113 | "id": 17445752, 114 | "indices": [ 115 | 60, 116 | 64 117 | ], 118 | "screen_name": "Cal" 119 | }, { 120 | "name": "Othman Laraki", 121 | "id_str": "20495814", 122 | "id": 20495814, 123 | "indices": [ 124 | 70, 125 | 77 126 | ], 127 | "screen_name": "othman" 128 | }] 129 | }, 130 | "text": "lecturing at the \"analyzing big data with twitter\" class at @cal with @othman http://t.co/bfj7zkDJ", 131 | "contributors": null, 132 | "id": 240556426106372096, 133 | "retweet_count": 3, 134 | "in_reply_to_status_id_str": null, 135 | "geo": { 136 | "coordinates": [ 137 | 37.871609, -122.25831 138 | ], 139 | "type": "Point" 140 | }, 141 | "retweeted": false, 142 | "possibly_sensitive": false, 143 | "in_reply_to_user_id": null, 144 | "place": { 145 | "name": "Berkeley", 146 | "country_code": "US", 147 | "country": "United States", 148 | "attributes": {}, 149 | "url": "http://api.twitter.com/1/geo/id/5ef5b7f391e30aff.json", 150 | "id": "5ef5b7f391e30aff", 151 | "bounding_box": { 152 | "coordinates": [ 153 | [ 154 | [-122.367781, 155 | 37.835727 156 | ], 157 | [-122.234185, 158 | 37.835727 159 | ], 160 | [-122.234185, 161 | 37.905824 162 | ], 163 | [-122.367781, 164 | 37.905824 165 | ] 166 | ] 167 | ], 168 | "type": "Polygon" 169 | }, 170 | "full_name": "Berkeley, CA", 171 | "place_type": "city" 172 | }, 173 | "source": "Safari on iOS", 174 | "user": { 175 | "name": "Raffi Krikorian", 176 | "profile_sidebar_fill_color": "DDEEF6", 177 | "profile_background_tile": false, 178 | "profile_sidebar_border_color": "C0DEED", 179 | "profile_image_url": "http://a0.twimg.com/profile_images/1270234259/raffi-headshot-casual_normal.png", 180 | "created_at": "Sun Aug 19 14:24:06 +0000 2007", 181 | "location": "San Francisco, California", 182 | "follow_request_sent": false, 183 | "id_str": "8285392", 184 | "is_translator": false, 185 | "profile_link_color": "0084B4", 186 | "entities": { 187 | "url": { 188 | "urls": [{ 189 | "expanded_url": "http://about.me/raffi.krikorian", 190 | "url": "http://t.co/eNmnM6q", 191 | "indices": [ 192 | 0, 193 | 19 194 | ], 195 | "display_url": "about.me/raffi.krikorian" 196 | }] 197 | }, 198 | "description": { 199 | "urls": [ 200 | 201 | ] 202 | } 203 | }, 204 | "default_profile": true, 205 | "url": "http://t.co/eNmnM6q", 206 | "contributors_enabled": false, 207 | "favourites_count": 724, 208 | "utc_offset": -28800, 209 | "profile_image_url_https": "https://si0.twimg.com/profile_images/1270234259/raffi-headshot-casual_normal.png", 210 | "id": 8285392, 211 | "listed_count": 619, 212 | "profile_use_background_image": true, 213 | "profile_text_color": "333333", 214 | "followers_count": 18752, 215 | "lang": "en", 216 | "protected": false, 217 | "geo_enabled": true, 218 | "notifications": false, 219 | "description": "Director of @twittereng's Platform Services. I break things.", 220 | "profile_background_color": "C0DEED", 221 | "verified": false, 222 | "time_zone": "Pacific Time (US & Canada)", 223 | "profile_background_image_url_https": "https://si0.twimg.com/images/themes/theme1/bg.png", 224 | "statuses_count": 5007, 225 | "profile_background_image_url": "http://a0.twimg.com/images/themes/theme1/bg.png", 226 | "default_profile_image": false, 227 | "friends_count": 701, 228 | "following": true, 229 | "show_all_inline_media": true, 230 | "screen_name": "raffi" 231 | }, 232 | "in_reply_to_screen_name": null, 233 | "in_reply_to_status_id": null 234 | }, { 235 | "coordinates": null, 236 | "truncated": false, 237 | "created_at": "Tue Aug 28 19:59:34 +0000 2012", 238 | "favorited": false, 239 | "id_str": "240539141056638977", 240 | "in_reply_to_user_id_str": null, 241 | "entities": { 242 | "urls": [ 243 | 244 | ], 245 | "hashtags": [ 246 | 247 | ], 248 | "user_mentions": [ 249 | 250 | ] 251 | }, 252 | "text": "You'd be right more often if you thought you were wrong.", 253 | "contributors": null, 254 | "id": 240539141056638977, 255 | "retweet_count": 1, 256 | "in_reply_to_status_id_str": null, 257 | "geo": null, 258 | "retweeted": false, 259 | "in_reply_to_user_id": null, 260 | "place": null, 261 | "source": "web", 262 | "user": { 263 | "name": "Taylor Singletary", 264 | "profile_sidebar_fill_color": "FBFBFB", 265 | "profile_background_tile": true, 266 | "profile_sidebar_border_color": "000000", 267 | "profile_image_url": "http://a0.twimg.com/profile_images/2546730059/f6a8zq58mg1hn0ha8vie_normal.jpeg", 268 | "created_at": "Wed Mar 07 22:23:19 +0000 2007", 269 | "location": "San Francisco, CA", 270 | "follow_request_sent": false, 271 | "id_str": "819797", 272 | "is_translator": false, 273 | "profile_link_color": "c71818", 274 | "entities": { 275 | "url": { 276 | "urls": [{ 277 | "expanded_url": "http://www.rebelmouse.com/episod/", 278 | "url": "http://t.co/Lxw7upbN", 279 | "indices": [ 280 | 0, 281 | 20 282 | ], 283 | "display_url": "rebelmouse.com/episod/" 284 | }] 285 | }, 286 | "description": { 287 | "urls": [ 288 | 289 | ] 290 | } 291 | }, 292 | "default_profile": false, 293 | "url": "http://t.co/Lxw7upbN", 294 | "contributors_enabled": false, 295 | "favourites_count": 15990, 296 | "utc_offset": -28800, 297 | "profile_image_url_https": "https://si0.twimg.com/profile_images/2546730059/f6a8zq58mg1hn0ha8vie_normal.jpeg", 298 | "id": 819797, 299 | "listed_count": 340, 300 | "profile_use_background_image": true, 301 | "profile_text_color": "D20909", 302 | "followers_count": 7126, 303 | "lang": "en", 304 | "protected": false, 305 | "geo_enabled": true, 306 | "notifications": false, 307 | "description": "Reality Technician, Twitter API team, synthesizer enthusiast; a most excellent adventure in timelines. I know it's hard to believe in something you can't see.", 308 | "profile_background_color": "000000", 309 | "verified": false, 310 | "time_zone": "Pacific Time (US & Canada)", 311 | "profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/643655842/hzfv12wini4q60zzrthg.png", 312 | "statuses_count": 18076, 313 | "profile_background_image_url": "http://a0.twimg.com/profile_background_images/643655842/hzfv12wini4q60zzrthg.png", 314 | "default_profile_image": false, 315 | "friends_count": 5444, 316 | "following": true, 317 | "show_all_inline_media": true, 318 | "screen_name": "episod" 319 | }, 320 | "in_reply_to_screen_name": null, 321 | "in_reply_to_status_id": null 322 | }] -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "disclaimer": "openFDA is a beta research project and not for clinical use. While we make every effort to ensure that data is accurate, you should assume all results are unvalidated.", 4 | "license": "http://open.fda.gov/license", 5 | "last_updated": "2016-02-05", 6 | "results": { 7 | "skip": 0, 8 | "limit": 1, 9 | "total": 4160934 10 | } 11 | }, 12 | "results": [ 13 | { 14 | "receiptdateformat": "102", 15 | "receiver": null, 16 | "companynumb": "HQWYE821915MAR04", 17 | "receivedateformat": "102", 18 | "primarysource": null, 19 | "seriousnessother": "1", 20 | "transmissiondateformat": "102", 21 | "fulfillexpeditecriteria": "1", 22 | "safetyreportid": "4322505-4", 23 | "sender": { 24 | "senderorganization": "FDA-Public Use" 25 | }, 26 | "receivedate": "20040319", 27 | "patient": { 28 | "patientonsetage": "56", 29 | "reaction": [ 30 | { 31 | "reactionmeddrapt": "ARTHRALGIA" 32 | }, 33 | { 34 | "reactionmeddrapt": "OEDEMA PERIPHERAL" 35 | }, 36 | { 37 | "reactionmeddrapt": "PURPURA" 38 | } 39 | ], 40 | "patientonsetageunit": "801", 41 | "patientsex": "1", 42 | "drug": [ 43 | { 44 | "drugstartdateformat": "102", 45 | "drugindication": "PYELONEPHRITIS", 46 | "drugadministrationroute": "048", 47 | "drugtreatmentduration": "4", 48 | "drugcharacterization": "1", 49 | "drugstartdate": "20031227", 50 | "drugenddateformat": "102", 51 | "drugtreatmentdurationunit": "804", 52 | "drugdosagetext": "ORAL", 53 | "drugauthorizationnumb": "50621", 54 | "drugenddate": "20031230", 55 | "medicinalproduct": "OROKEN (CEFIXIME, UNSPEC)" 56 | }, 57 | { 58 | "drugcharacterization": "2", 59 | "medicinalproduct": "PYOSTACINE (PRISTINAMYCIN)" 60 | }, 61 | { 62 | "drugcharacterization": "2", 63 | "openfda": { 64 | "product_ndc": [ 65 | "0004-1963", 66 | "0004-1964" 67 | ], 68 | "nui": [ 69 | "N0000011161", 70 | "N0000175488" 71 | ], 72 | "package_ndc": [ 73 | "0004-1964-04", 74 | "0004-1963-02", 75 | "0004-1963-01", 76 | "0004-1964-01" 77 | ], 78 | "generic_name": [ 79 | "CEFTRIAXONE SODIUM" 80 | ], 81 | "spl_set_id": [ 82 | "9467f6c9-3e59-45c6-a1be-77200f2d4554" 83 | ], 84 | "pharm_class_cs": [ 85 | "Cephalosporins [Chemical/Ingredient]" 86 | ], 87 | "brand_name": [ 88 | "ROCEPHIN" 89 | ], 90 | "manufacturer_name": [ 91 | "Genentech, Inc." 92 | ], 93 | "unii": [ 94 | "75J73V1629" 95 | ], 96 | "rxcui": [ 97 | "204871", 98 | "105212" 99 | ], 100 | "spl_id": [ 101 | "86e3103c-9d8b-4693-b5db-3fd62330c754" 102 | ], 103 | "substance_name": [ 104 | "CEFTRIAXONE SODIUM" 105 | ], 106 | "product_type": [ 107 | "HUMAN PRESCRIPTION DRUG" 108 | ], 109 | "route": [ 110 | "INTRAMUSCULAR", 111 | "INTRAVENOUS" 112 | ], 113 | "application_number": [ 114 | "ANDA063239" 115 | ], 116 | "pharm_class_epc": [ 117 | "Cephalosporin Antibacterial [EPC]" 118 | ] 119 | }, 120 | "medicinalproduct": "ROCEPHIN" 121 | }, 122 | { 123 | "drugcharacterization": "2", 124 | "openfda": { 125 | "product_ndc": [ 126 | "55111-160", 127 | "55111-161", 128 | "55111-162", 129 | "16571-130", 130 | "60505-0560", 131 | "65691-0105", 132 | "65691-0104", 133 | "65691-0106", 134 | "11980-779", 135 | "61314-015", 136 | "50383-025", 137 | "50383-024", 138 | "61314-012", 139 | "0093-7181", 140 | "60505-0363", 141 | "40042-049", 142 | "0093-7180", 143 | "59390-140", 144 | "0093-7182", 145 | "24208-434", 146 | "17478-713", 147 | "24208-410" 148 | ], 149 | "nui": [ 150 | "N0000175937", 151 | "N0000007606" 152 | ], 153 | "package_ndc": [ 154 | "16571-130-50", 155 | "55111-161-50", 156 | "61314-012-05", 157 | "17478-713-11", 158 | "17478-713-10", 159 | "55111-160-78", 160 | "55111-161-78", 161 | "55111-162-78", 162 | "55111-160-30", 163 | "0093-7181-01", 164 | "65691-0106-2", 165 | "65691-0106-1", 166 | "50383-024-10", 167 | "65691-0105-1", 168 | "65691-0105-3", 169 | "65691-0105-2", 170 | "0093-7182-01", 171 | "60505-0363-1", 172 | "24208-434-05", 173 | "0093-7180-01", 174 | "61314-015-05", 175 | "55111-161-01", 176 | "65691-0104-1", 177 | "65691-0104-2", 178 | "65691-0104-3", 179 | "55111-161-05", 180 | "50383-025-10", 181 | "40042-049-10", 182 | "55111-162-01", 183 | "50383-025-05", 184 | "55111-162-05", 185 | "61314-012-10", 186 | "16571-130-11", 187 | "55111-160-01", 188 | "55111-160-05", 189 | "50383-024-05", 190 | "55111-162-30", 191 | "60505-0363-2", 192 | "24208-410-10", 193 | "55111-162-50", 194 | "24208-434-10", 195 | "24208-410-05", 196 | "60505-0560-1", 197 | "60505-0560-0", 198 | "61314-015-10", 199 | "59390-140-05", 200 | "40042-049-05", 201 | "55111-161-30", 202 | "55111-160-50", 203 | "11980-779-05" 204 | ], 205 | "generic_name": [ 206 | "OFLOXACIN", 207 | "OFLOXAXIN" 208 | ], 209 | "spl_set_id": [ 210 | "8db221b1-32f3-f6ca-e404-71f56a860d08", 211 | "1d19a6db-6da5-e7de-f929-2d18bdfa2cf5", 212 | "95b9fc17-9c94-4762-910c-df0bb0b2aa85", 213 | "7aab4449-3dda-4e2c-8e40-b3244a548bf5", 214 | "81e8ece1-ab0b-4deb-8f84-79f21419b328", 215 | "2ec6bd57-96df-47ac-b218-1469801868b7", 216 | "7882f70e-d228-4c52-9390-0d927c51af1f", 217 | "93a464f1-6b54-4f4e-8b71-fa781e2964e6", 218 | "fe564e33-8f5d-4b57-87a9-7afae02eaf97", 219 | "ad40954d-0b1d-47c5-8bd5-8a1efd9a7153", 220 | "8b73d354-4631-40e8-b187-e8b0580bd6ea", 221 | "749177b8-1c9e-4bc2-81fd-27d7e88cf9b8", 222 | "b199f48d-647c-4041-a3ee-33737c515152", 223 | "5117d567-2004-c5ed-1391-f8831864696f", 224 | "c5484f1a-2321-453f-9469-dfcf709e2e2e", 225 | "33ad989c-f551-4c57-a4e3-b607686206fa" 226 | ], 227 | "pharm_class_cs": [ 228 | "Quinolones [Chemical/Ingredient]" 229 | ], 230 | "brand_name": [ 231 | "OFLOXACIN OTIC", 232 | "OFLOXACIN OPHTHALMIC", 233 | "OFLOXACIN", 234 | "OCUFLOX" 235 | ], 236 | "manufacturer_name": [ 237 | "Akorn, Inc.", 238 | "Altaire Pharmaceuticals Inc.", 239 | "Dr. Reddy's Laboratories Limited", 240 | "Pack Pharmaceuticals, LLC", 241 | "Hi-Tech Pharmacal Co., Inc.", 242 | "Allergan, Inc.", 243 | "Bausch & Lomb Incorporated", 244 | "Apotex Corp.", 245 | "Cadila Pharmaceuticals Limited", 246 | "PharmaForce, Inc.", 247 | "Falcon Pharmaceuticals, Ltd.", 248 | "Teva Pharmaceuticals USA Inc" 249 | ], 250 | "unii": [ 251 | "A4P49JAZ9H" 252 | ], 253 | "rxcui": [ 254 | "207202", 255 | "312075", 256 | "198048", 257 | "198049", 258 | "198050", 259 | "242446" 260 | ], 261 | "spl_id": [ 262 | "b2235a46-f526-471e-8a9c-62f943ef6f7b", 263 | "1419e0b7-cb1e-48fd-ab83-edfa70ddd693", 264 | "4ebe9909-6509-ab0a-931c-e70bd210125f", 265 | "4d4af447-be12-4ca8-89ce-4393ee8bcc9e", 266 | "10d0fd2b-81f4-4836-b279-cf2b17813eb5", 267 | "8ec38b43-795a-45ba-bbd3-8ebd6e1fcdf2", 268 | "243c0b7c-8011-4a94-8b25-0329de936439", 269 | "52572d94-d47b-428f-acc7-67147c5b0681", 270 | "0095d1d6-d83e-4e22-aa61-3b4753f0753b", 271 | "349789cb-da00-4eeb-88fa-a949263c1618", 272 | "c3b5609c-3d6a-4c9a-8f81-611d0fa19180", 273 | "3665d266-c322-45b4-9e56-caa6035b092c", 274 | "568bf875-beda-4425-b175-decd86dcae12", 275 | "7bc4742b-6f34-2c2d-95b9-d70f8358e55f", 276 | "abfa4cd4-5c6d-62cd-3d3a-ff5a393b8301", 277 | "1f88e5a7-78c8-4f48-81bd-5fd664cee9c8" 278 | ], 279 | "substance_name": [ 280 | "OFLOXACIN" 281 | ], 282 | "product_type": [ 283 | "HUMAN PRESCRIPTION DRUG" 284 | ], 285 | "route": [ 286 | "AURICULAR (OTIC)", 287 | "ORAL", 288 | "OPHTHALMIC" 289 | ], 290 | "application_number": [ 291 | "ANDA076407", 292 | "ANDA076182", 293 | "NDA019921", 294 | "ANDA091656", 295 | "ANDA076128", 296 | "ANDA078222", 297 | "ANDA090395", 298 | "ANDA076527", 299 | "ANDA076622", 300 | "ANDA076513", 301 | "ANDA076616", 302 | "ANDA202692", 303 | "ANDA078559", 304 | "ANDA076615", 305 | "ANDA076231", 306 | "ANDA077098" 307 | ], 308 | "pharm_class_epc": [ 309 | "Quinolone Antimicrobial [EPC]" 310 | ] 311 | }, 312 | "medicinalproduct": "OFLOXACIN" 313 | } 314 | ] 315 | }, 316 | "seriousnesshospitalization": "1", 317 | "transmissiondate": "20041129", 318 | "serious": "1", 319 | "receiptdate": "20040315" 320 | } 321 | ] 322 | } -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/weatherService.json: -------------------------------------------------------------------------------- 1 | {"cod":"200","message":2.2656,"city":{"id":2633583,"name":"Woolwich","coord":{"lon":0.0648,"lat":51.491001},"country":"GB","population":0},"cnt":36,"list":[{"dt":1424444400,"main":{"temp":6.5,"temp_min":6.5,"temp_max":7,"pressure":1003.1,"sea_level":1011,"grnd_level":1003.1,"humidity":100,"temp_kf":-0.5},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":88},"wind":{"speed":5.08,"deg":317.5},"rain":{"3h":3},"sys":{"pod":"d"},"dt_txt":"2015-02-20 15:00:00"},{"dt":1424455200,"main":{"temp":3.11,"temp_min":3.11,"temp_max":3.59,"pressure":1004.94,"sea_level":1012.82,"grnd_level":1004.94,"humidity":95,"temp_kf":-0.47},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"clouds":{"all":36},"wind":{"speed":3.95,"deg":314.503},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-20 18:00:00"},{"dt":1424466000,"main":{"temp":3.54,"temp_min":3.54,"temp_max":3.99,"pressure":1005.99,"sea_level":1013.83,"grnd_level":1005.99,"humidity":95,"temp_kf":-0.45},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"clouds":{"all":48},"wind":{"speed":3.47,"deg":288.502},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-20 21:00:00"},{"dt":1424476800,"main":{"temp":1.57,"temp_min":1.57,"temp_max":2,"pressure":1005.57,"sea_level":1013.6,"grnd_level":1005.57,"humidity":98,"temp_kf":-0.42},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":2.97,"deg":262.501},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-21 00:00:00"},{"dt":1424487600,"main":{"temp":2.07,"temp_min":2.07,"temp_max":2.47,"pressure":1005.16,"sea_level":1013.21,"grnd_level":1005.16,"humidity":99,"temp_kf":-0.4},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"clouds":{"all":68},"wind":{"speed":2.81,"deg":267.504},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-21 03:00:00"},{"dt":1424498400,"main":{"temp":2.51,"temp_min":2.51,"temp_max":2.88,"pressure":1005.92,"sea_level":1013.81,"grnd_level":1005.92,"humidity":98,"temp_kf":-0.37},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"clouds":{"all":88},"wind":{"speed":2.72,"deg":294.002},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-21 06:00:00"},{"dt":1424509200,"main":{"temp":3.06,"temp_min":3.06,"temp_max":3.41,"pressure":1007.18,"sea_level":1015.22,"grnd_level":1007.18,"humidity":99,"temp_kf":-0.35},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"clouds":{"all":48},"wind":{"speed":2.76,"deg":296.502},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-21 09:00:00"},{"dt":1424520000,"main":{"temp":4.38,"temp_min":4.38,"temp_max":4.7,"pressure":1008.11,"sea_level":1016.01,"grnd_level":1008.11,"humidity":96,"temp_kf":-0.32},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"clouds":{"all":48},"wind":{"speed":2.86,"deg":299.01},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-21 12:00:00"},{"dt":1424530800,"main":{"temp":5.04,"temp_min":5.04,"temp_max":5.34,"pressure":1008.02,"sea_level":1015.94,"grnd_level":1008.02,"humidity":92,"temp_kf":-0.3},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"clouds":{"all":12},"wind":{"speed":2.62,"deg":284.501},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-21 15:00:00"},{"dt":1424541600,"main":{"temp":3.55,"temp_min":3.55,"temp_max":3.82,"pressure":1008.92,"sea_level":1016.96,"grnd_level":1008.92,"humidity":90,"temp_kf":-0.27},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10n"}],"clouds":{"all":92},"wind":{"speed":2.67,"deg":281.502},"rain":{"3h":1},"sys":{"pod":"n"},"dt_txt":"2015-02-21 18:00:00"},{"dt":1424552400,"main":{"temp":2.43,"temp_min":2.43,"temp_max":2.68,"pressure":1010.04,"sea_level":1018.1,"grnd_level":1010.04,"humidity":94,"temp_kf":-0.25},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":2.76,"deg":286.506},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-21 21:00:00"},{"dt":1424563200,"main":{"temp":0.95,"temp_min":0.95,"temp_max":1.17,"pressure":1010.98,"sea_level":1019.04,"grnd_level":1010.98,"humidity":92,"temp_kf":-0.22},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}],"clouds":{"all":12},"wind":{"speed":2.97,"deg":287.5},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-22 00:00:00"},{"dt":1424574000,"main":{"temp":0.03,"temp_min":0.03,"temp_max":0.23,"pressure":1011.81,"sea_level":1019.79,"grnd_level":1011.81,"humidity":91,"temp_kf":-0.2},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":2.66,"deg":277.504},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-22 03:00:00"},{"dt":1424584800,"main":{"temp":-1.13,"temp_min":-1.13,"temp_max":-0.95,"pressure":1012.8,"sea_level":1020.91,"grnd_level":1012.8,"humidity":94,"temp_kf":-0.17},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":2.51,"deg":262.501},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-22 06:00:00"},{"dt":1424595600,"main":{"temp":1.12,"temp_min":1.12,"temp_max":1.27,"pressure":1013.48,"sea_level":1021.63,"grnd_level":1013.48,"humidity":92,"temp_kf":-0.15},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"clouds":{"all":12},"wind":{"speed":2.06,"deg":246.506},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-22 09:00:00"},{"dt":1424606400,"main":{"temp":6.04,"temp_min":6.04,"temp_max":6.17,"pressure":1013.04,"sea_level":1020.95,"grnd_level":1013.04,"humidity":90,"temp_kf":-0.12},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"clouds":{"all":24},"wind":{"speed":2.21,"deg":220.004},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-22 12:00:00"},{"dt":1424617200,"main":{"temp":6.62,"temp_min":6.62,"temp_max":6.72,"pressure":1010.12,"sea_level":1017.99,"grnd_level":1010.12,"humidity":79,"temp_kf":-0.1},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"clouds":{"all":68},"wind":{"speed":4.1,"deg":206.003},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-22 15:00:00"},{"dt":1424628000,"main":{"temp":5.32,"temp_min":5.32,"temp_max":5.4,"pressure":1006.64,"sea_level":1014.68,"grnd_level":1006.64,"humidity":84,"temp_kf":-0.07},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10n"}],"clouds":{"all":92},"wind":{"speed":5.47,"deg":198.501},"rain":{"3h":1},"sys":{"pod":"n"},"dt_txt":"2015-02-22 18:00:00"},{"dt":1424638800,"main":{"temp":5.17,"temp_min":5.17,"temp_max":5.22,"pressure":1001.47,"sea_level":1009.34,"grnd_level":1001.47,"humidity":97,"temp_kf":-0.05},"weather":[{"id":501,"main":"Rain","description":"moderate rain","icon":"10n"}],"clouds":{"all":92},"wind":{"speed":7.01,"deg":193.505},"rain":{"3h":4},"sys":{"pod":"n"},"dt_txt":"2015-02-22 21:00:00"},{"dt":1424649600,"main":{"temp":8.38,"temp_min":8.38,"temp_max":8.41,"pressure":995.87,"sea_level":1003.83,"grnd_level":995.87,"humidity":96,"temp_kf":-0.02},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10n"}],"clouds":{"all":92},"wind":{"speed":5.11,"deg":224.005},"rain":{"3h":3},"sys":{"pod":"n"},"dt_txt":"2015-02-23 00:00:00"},{"dt":1424660400,"main":{"temp":8.58,"temp_min":8.58,"temp_max":8.58,"pressure":996.5,"sea_level":1004.41,"grnd_level":996.5,"humidity":95},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10n"}],"clouds":{"all":20},"wind":{"speed":4.66,"deg":274.501},"rain":{"3h":1},"sys":{"pod":"n"},"dt_txt":"2015-02-23 03:00:00"},{"dt":1424671200,"main":{"temp":4.77,"temp_min":4.77,"temp_max":4.77,"pressure":998.18,"sea_level":1005.95,"grnd_level":998.18,"humidity":95},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}],"clouds":{"all":20},"wind":{"speed":4.02,"deg":265},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-23 06:00:00"},{"dt":1424682000,"main":{"temp":4.74,"temp_min":4.74,"temp_max":4.74,"pressure":998.81,"sea_level":1006.76,"grnd_level":998.81,"humidity":95},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"clouds":{"all":0},"wind":{"speed":3.75,"deg":252.5},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-23 09:00:00"},{"dt":1424692800,"main":{"temp":7.19,"temp_min":7.19,"temp_max":7.19,"pressure":997.37,"sea_level":1005.1,"grnd_level":997.37,"humidity":95},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"clouds":{"all":0},"wind":{"speed":4.41,"deg":226.001},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-23 12:00:00"},{"dt":1424703600,"main":{"temp":6.26,"temp_min":6.26,"temp_max":6.26,"pressure":993.8,"sea_level":1001.62,"grnd_level":993.8,"humidity":100},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":92},"wind":{"speed":4.72,"deg":209.002},"rain":{"3h":2},"sys":{"pod":"d"},"dt_txt":"2015-02-23 15:00:00"},{"dt":1424714400,"main":{"temp":5.04,"temp_min":5.04,"temp_max":5.04,"pressure":993.58,"sea_level":1001.35,"grnd_level":993.58,"humidity":96},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10n"}],"clouds":{"all":48},"wind":{"speed":4.51,"deg":257.501},"rain":{"3h":1},"sys":{"pod":"n"},"dt_txt":"2015-02-23 18:00:00"},{"dt":1424725200,"main":{"temp":4.34,"temp_min":4.34,"temp_max":4.34,"pressure":994.03,"sea_level":1001.83,"grnd_level":994.03,"humidity":93},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"clouds":{"all":48},"wind":{"speed":5.22,"deg":247.502},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-23 21:00:00"},{"dt":1424736000,"main":{"temp":4.46,"temp_min":4.46,"temp_max":4.46,"pressure":993.61,"sea_level":1001.36,"grnd_level":993.61,"humidity":92},"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01n"}],"clouds":{"all":0},"wind":{"speed":5.67,"deg":243.501},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-24 00:00:00"},{"dt":1424746800,"main":{"temp":4.65,"temp_min":4.65,"temp_max":4.65,"pressure":992.95,"sea_level":1000.76,"grnd_level":992.95,"humidity":95},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"clouds":{"all":92},"wind":{"speed":5.71,"deg":248.011},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-24 03:00:00"},{"dt":1424757600,"main":{"temp":4.72,"temp_min":4.72,"temp_max":4.72,"pressure":993.86,"sea_level":1001.71,"grnd_level":993.86,"humidity":91},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02n"}],"clouds":{"all":20},"wind":{"speed":5.36,"deg":261.507},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-24 06:00:00"},{"dt":1424768400,"main":{"temp":5.76,"temp_min":5.76,"temp_max":5.76,"pressure":995.05,"sea_level":1002.92,"grnd_level":995.05,"humidity":88},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"clouds":{"all":20},"wind":{"speed":5.61,"deg":257.001},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-24 09:00:00"},{"dt":1424779200,"main":{"temp":7.53,"temp_min":7.53,"temp_max":7.53,"pressure":996.11,"sea_level":1003.8,"grnd_level":996.11,"humidity":78},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"clouds":{"all":48},"wind":{"speed":6.01,"deg":263.503},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-24 12:00:00"},{"dt":1424790000,"main":{"temp":7.54,"temp_min":7.54,"temp_max":7.54,"pressure":996.7,"sea_level":1004.35,"grnd_level":996.7,"humidity":73},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"clouds":{"all":56},"wind":{"speed":5.67,"deg":266.004},"rain":{"3h":0},"sys":{"pod":"d"},"dt_txt":"2015-02-24 15:00:00"},{"dt":1424800800,"main":{"temp":6.5,"temp_min":6.5,"temp_max":6.5,"pressure":998.22,"sea_level":1006.07,"grnd_level":998.22,"humidity":71},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"clouds":{"all":56},"wind":{"speed":5.47,"deg":269.002},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-24 18:00:00"},{"dt":1424811600,"main":{"temp":5.95,"temp_min":5.95,"temp_max":5.95,"pressure":1000.65,"sea_level":1008.4,"grnd_level":1000.65,"humidity":72},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"clouds":{"all":88},"wind":{"speed":5.91,"deg":278.501},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-24 21:00:00"},{"dt":1424822400,"main":{"temp":5.97,"temp_min":5.97,"temp_max":5.97,"pressure":1003.1,"sea_level":1011.03,"grnd_level":1003.1,"humidity":72},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"clouds":{"all":44},"wind":{"speed":5.72,"deg":289},"rain":{"3h":0},"sys":{"pod":"n"},"dt_txt":"2015-02-25 00:00:00"}]} 2 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/cycloneForecast.json: -------------------------------------------------------------------------------- 1 | { 2 | "currentVersion": 10.22, 3 | "id": 3, 4 | "name": "Tropical Cyclone Center Position Forecasts", 5 | "type": "Feature Layer", 6 | "description": "", 7 | "geometryType": "esriGeometryPoint", 8 | "copyrightText": "", 9 | "parentLayer": { 10 | "id": 1, 11 | "name": "Tropical Cyclone Track and Intensity Forecasts" 12 | }, 13 | "subLayers": [], 14 | "minScale": 0, 15 | "maxScale": 0, 16 | "drawingInfo": { 17 | "renderer": { 18 | "type": "uniqueValue", 19 | "field1": "dvlbl", 20 | "field2": null, 21 | "field3": null, 22 | "fieldDelimiter": ", ", 23 | "defaultSymbol": null, 24 | "defaultLabel": null, 25 | "uniqueValueInfos": [ 26 | { 27 | "symbol": { 28 | "type": "esriPMS", 29 | "url": "49e6feb7e7a58530d991be4a971aee9a", 30 | "imageData": "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAYCAYAAAAlBadpAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAmFJREFUOI2llE1IVFEUx3/XnqMOVFaYvle4a0ap/OjpQggKqRY2LnLhSiIiny2KFqEUCBK0CKGgTcEEQasCoQ9mCIIWQ1JC+tLow8xIR0xnsEYLv8aPbgvfG576nILO9p7f/f/vuecchf8IJd2hlFIHNCFE6J9hKaUBGDOJSf338jJByDJg4a+wlDK0mEwGvr56TUGxj60F+TRKecoQIpgWtsHue/epaKjH4/WaQBgw09q2rAb6Op6w58hhPF5vGGgSQoy5gWuVjZnEJBNv3lPZUI+lZkgpxwBTCLFOXbFUawF9xOxDO1gJwPfhkbb4p88szSfZVribxeRCr+LJDDid2MoqQPxdPzv3+oncuEX04oNVKrmn9fLqa619Usoy+wIb1gCmh0aJPY6Q7Iyte9/UXZNIVntezc2rd4DjTlgHmIvGXEE7Ere7+HbmY42UUhdCmKv/Wdm0IWjHaE8vhQdKdSAFjwHkVZUQfTSQFv75JZpyasNBwPAFjhFt6UgLy6VlsGqkAAghTCnluFbsV0sftvC2rt0VzNiVzY79/pRT55trgZ6yEwHy+30MhJ4Re/qSpcQs24+W46+rYTD8HLV0H8D4KthSrwBCapFPVYt80Hw+dfP89Azdl66jtjUDhNYqY7WgZvW5ZhVGB9TBSCclrWfJzPKE7VZ1nWdhjZ+1DHp+jIwS7/1A9eULsFJcXGEL0K0aBGIDgwy96OJQ8zkyFCXo3CpuysbC7JwxOzXFr/gE2Vs2U9V4EiAohGhyJrrB4x5vDh5vDrmaCiujecVtj7nBzqSQ2xxvCFvJGwLO+ANkl+oACm+FFgAAAABJRU5ErkJggg==", 31 | "contentType": "image/png", 32 | "width": 11, 33 | "height": 18, 34 | "angle": 0, 35 | "xoffset": 0, 36 | "yoffset": 0 37 | }, 38 | "value": "M", 39 | "label": "Major Hurricane", 40 | "description": "" 41 | }, 42 | { 43 | "symbol": { 44 | "type": "esriPMS", 45 | "url": "49e6feb7e7a58530d991be4a971aee9a", 46 | "imageData": "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAYCAYAAAAlBadpAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAmFJREFUOI2llE1IVFEUx3/XnqMOVFaYvle4a0ap/OjpQggKqRY2LnLhSiIiny2KFqEUCBK0CKGgTcEEQasCoQ9mCIIWQ1JC+tLow8xIR0xnsEYLv8aPbgvfG576nILO9p7f/f/vuecchf8IJd2hlFIHNCFE6J9hKaUBGDOJSf338jJByDJg4a+wlDK0mEwGvr56TUGxj60F+TRKecoQIpgWtsHue/epaKjH4/WaQBgw09q2rAb6Op6w58hhPF5vGGgSQoy5gWuVjZnEJBNv3lPZUI+lZkgpxwBTCLFOXbFUawF9xOxDO1gJwPfhkbb4p88szSfZVribxeRCr+LJDDid2MoqQPxdPzv3+oncuEX04oNVKrmn9fLqa619Usoy+wIb1gCmh0aJPY6Q7Iyte9/UXZNIVntezc2rd4DjTlgHmIvGXEE7Ere7+HbmY42UUhdCmKv/Wdm0IWjHaE8vhQdKdSAFjwHkVZUQfTSQFv75JZpyasNBwPAFjhFt6UgLy6VlsGqkAAghTCnluFbsV0sftvC2rt0VzNiVzY79/pRT55trgZ6yEwHy+30MhJ4Re/qSpcQs24+W46+rYTD8HLV0H8D4KthSrwBCapFPVYt80Hw+dfP89Azdl66jtjUDhNYqY7WgZvW5ZhVGB9TBSCclrWfJzPKE7VZ1nWdhjZ+1DHp+jIwS7/1A9eULsFJcXGEL0K0aBGIDgwy96OJQ8zkyFCXo3CpuysbC7JwxOzXFr/gE2Vs2U9V4EiAohGhyJrrB4x5vDh5vDrmaCiujecVtj7nBzqSQ2xxvCFvJGwLO+ANkl+oACm+FFgAAAABJRU5ErkJggg==", 47 | "contentType": "image/png", 48 | "width": 11, 49 | "height": 18, 50 | "angle": 0, 51 | "xoffset": 0, 52 | "yoffset": 0 53 | }, 54 | "value": "H", 55 | "label": "Hurricane", 56 | "description": "" 57 | }, 58 | { 59 | "symbol": { 60 | "type": "esriPMS", 61 | "url": "8a1d420b82ac6db58671af96d5f38aa5", 62 | "imageData": "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAYCAYAAAAlBadpAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAqlJREFUOI2llE1IVFEYhp9j06gDlaGm91pRUDP+pebNhZQUERE2RgW5SaJF3loEtpEsLAvBRRBECxfTIiKiRRHFuAoiM6jImRwoS1MqRR0H8xfHn0k7Lbx3uNloQd/2nue87/fd9zs2/qNsy32UUmqAKoTw/jMspdQBPTwyqv2cn8cD8TpE/gpLKb0/ZmfdX169JT3LyZr0NCqlPKkL4VkWNsHWO/fZUVGO3eHwA02Af1nbhlV34METtu7bg93haAJOCyEGYoGLlfXwyChD7z5QVFGOoaZLKQcAvxDiD3WboVoGaL3+AOquIgC+f+utC3V8Zm5mlrUb1/NjNtJms690W52YygpA6P0n1uW4aLnpIWnTejZoBYi4OAbbO3haU7+95EJVQEpZYF5gwirA5Nc+Bh83U9J4hYzcbIAgoCQp6aRnu2hpuJF64NrlW8BBK6wBTPcMkry3yAQPCSG8RlC8SaqipBbm0v/+Y6mUUhNC+H//z7YVpGxzAXjMVAkh/MZMfMlbNtPna2NjYb4GROEBgNTiPKZHRk271lIBZsYnGO/uiTo1YQ+gO937eX78PAXHDtdJKT1CiAHDtj4XidB59xEJaSnRy2wWa0E1y6XkXDrFs4sNlNSe65dSBgEmh0eUN423Cb/oJqN+Z9SptecywFdwxE1alpPAvYdMBYcUAOIErqOlzE3PoOTnRtuKwob6DsCrZDoVJdP5W9Mzk2Faa66j1FUDeBcrY0RQNXKuGoPRAKWr+SV5tWdYGW9vMqMac5+FsX7GsHzDvX2E2trZe6HKHC4xYQPQjBm4Bzu7+Nrymt3VZ4mz2TzWVyWWsh6ZmtanxsaYCA2RsHoVxZUnYCE4p60HY8FBuyMRuyORJFWBhdW8GusdiwVbD3lj7fGSsHF4ScBavwAEng2YMSFnTgAAAABJRU5ErkJggg==", 63 | "contentType": "image/png", 64 | "width": 11, 65 | "height": 18, 66 | "angle": 0, 67 | "xoffset": 0, 68 | "yoffset": 0 69 | }, 70 | "value": "S", 71 | "label": "Tropical Storm", 72 | "description": "" 73 | }, 74 | { 75 | "symbol": { 76 | "type": "esriPMS", 77 | "url": "27b45b40b587111d9ca58aa74af566d2", 78 | "imageData": "iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAShJREFUKJFtkqGOwlAQRc8kxW2CffANRWz4hcrWVVTgkYRU1oB4ybqamkWTtL78xdJkXf+gYV0RVYhZQ1+6ZK+bN3fenZk7Hi9Q1SWwBjoRaV7z3oS4Bg5AOHkDuABbEelcwZQ8DANt29L3Pb7vY4wJgSuwnCocgPB2u1EUBdZa10KSJOR5vlDVWkQi79lzOAwDRVHwbT/54B3DG3cenKuaPVCWZTgqrAHatsVa68gAc2ZsWLGrKtI0RVUjD+gA+r4HcOQRc2ZM8p0nIo2q4vs+AHcejjTG/631YowJkyThXNVsWDFnxp0HX/yQZRlBEJxEpBkLtsA1z/PFHthVlfsxyzLiOG6Ao1N4mrJU1bosyzBNUzdTEAQn4PjHuBEiEj2NjMZlvJ7HL7bQf7cPRFVkAAAAAElFTkSuQmCC", 79 | "contentType": "image/png", 80 | "width": 9, 81 | "height": 9, 82 | "angle": 0, 83 | "xoffset": 0, 84 | "yoffset": 0 85 | }, 86 | "value": "D", 87 | "label": "Tropical Depression", 88 | "description": "" 89 | }, 90 | { 91 | "symbol": { 92 | "type": "esriPMS", 93 | "url": "251e80ce771724706aabe3c1020fee3f", 94 | "imageData": "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAUCAYAAAC9BQwsAAAAAXNSR0IB2cksfwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAP5JREFUOI3tkbFKw2AYRc9Xo5JqoWvi5BM4RBAHH8TRJaub4GSddHK3s29gBkeXooNF8AUEK7EOLoLE1t9eB9sQxDbVwck7/ffC4Tvwe/wy3ughqQEEw5qY2WkpKCl6d26vc30DwKzvx4BNczGe8TzuLq643T5h/fwASbGZNcvAB4D52gIAc1U/38rAH+cf/GswkBQVBzNrTwRdr08/y46/7k+de6r1+qFfW9z9Fmxt7NAao7H5crYF5GAA8Ja9TjD/jKRuUbU5cC5eilYIL4/GQs9pFw30mINm1pa0v7y2GgBh0WKYEAiQEkFSvIiZNUoUQ6tU0lGf+h/NLC32Dyy4UZiQLwmGAAAAAElFTkSuQmCC", 95 | "contentType": "image/png", 96 | "width": 10, 97 | "height": 15, 98 | "angle": 0, 99 | "xoffset": 0, 100 | "yoffset": 0 101 | }, 102 | "value": "L", 103 | "label": "Remnant Low", 104 | "description": "" 105 | } 106 | ] 107 | }, 108 | "transparency": 0, 109 | "labelingInfo": null 110 | }, 111 | "defaultVisibility": true, 112 | "extent": { 113 | "xmin": -2.0037507842788246E7, 114 | "ymin": -3.024097145838617E7, 115 | "xmax": 2.0037507842788246E7, 116 | "ymax": 3.0240971458386205E7, 117 | "spatialReference": { 118 | "wkid": 102100, 119 | "latestWkid": 3857 120 | } 121 | }, 122 | "timeInfo": { 123 | "startTimeField": "starttime", 124 | "endTimeField": "endtime", 125 | "trackIdField": null, 126 | "timeExtent": [ 127 | 978307200000, 128 | 978307200000 129 | ], 130 | "timeReference": { 131 | "timeZone": "UTC", 132 | "respectsDaylightSaving": false 133 | }, 134 | "timeInterval": 1, 135 | "timeIntervalUnits": "esriTimeUnitsHours", 136 | "exportOptions": { 137 | "useTime": true, 138 | "timeDataCumulative": false, 139 | "timeOffset": null, 140 | "timeOffsetUnits": null 141 | }, 142 | "hasLiveData": true 143 | }, 144 | "hasAttachments": false, 145 | "htmlPopupType": "esriServerHTMLPopupTypeAsHTMLText", 146 | "displayField": "stormname", 147 | "typeIdField": null, 148 | "fields": [ 149 | { 150 | "name": "ogc_fid", 151 | "type": "esriFieldTypeOID", 152 | "alias": "ogc_fid", 153 | "domain": null 154 | }, 155 | { 156 | "name": "wkb_geometry", 157 | "type": "esriFieldTypeGeometry", 158 | "alias": "wkb_geometry", 159 | "domain": null 160 | }, 161 | { 162 | "name": "stormname", 163 | "type": "esriFieldTypeString", 164 | "alias": "stormname", 165 | "length": 255, 166 | "domain": null 167 | }, 168 | { 169 | "name": "stormtype", 170 | "type": "esriFieldTypeString", 171 | "alias": "stormtype", 172 | "length": 255, 173 | "domain": null 174 | }, 175 | { 176 | "name": "advdate", 177 | "type": "esriFieldTypeString", 178 | "alias": "advdate", 179 | "length": 255, 180 | "domain": null 181 | }, 182 | { 183 | "name": "advisnum", 184 | "type": "esriFieldTypeString", 185 | "alias": "advisnum", 186 | "length": 255, 187 | "domain": null 188 | }, 189 | { 190 | "name": "stormnum", 191 | "type": "esriFieldTypeDouble", 192 | "alias": "stormnum", 193 | "domain": null 194 | }, 195 | { 196 | "name": "fcstprd", 197 | "type": "esriFieldTypeDouble", 198 | "alias": "fcstprd", 199 | "domain": null 200 | }, 201 | { 202 | "name": "basin", 203 | "type": "esriFieldTypeString", 204 | "alias": "basin", 205 | "length": 255, 206 | "domain": null 207 | }, 208 | { 209 | "name": "lat", 210 | "type": "esriFieldTypeDouble", 211 | "alias": "lat", 212 | "domain": null 213 | }, 214 | { 215 | "name": "lon", 216 | "type": "esriFieldTypeDouble", 217 | "alias": "lon", 218 | "domain": null 219 | }, 220 | { 221 | "name": "tau", 222 | "type": "esriFieldTypeDouble", 223 | "alias": "tau", 224 | "domain": null 225 | }, 226 | { 227 | "name": "maxwind", 228 | "type": "esriFieldTypeDouble", 229 | "alias": "maxwind", 230 | "domain": null 231 | }, 232 | { 233 | "name": "gust", 234 | "type": "esriFieldTypeDouble", 235 | "alias": "gust", 236 | "domain": null 237 | }, 238 | { 239 | "name": "mslp", 240 | "type": "esriFieldTypeDouble", 241 | "alias": "mslp", 242 | "domain": null 243 | }, 244 | { 245 | "name": "tcdvlp", 246 | "type": "esriFieldTypeString", 247 | "alias": "tcdvlp", 248 | "length": 255, 249 | "domain": null 250 | }, 251 | { 252 | "name": "dvlbl", 253 | "type": "esriFieldTypeString", 254 | "alias": "dvlbl", 255 | "length": 255, 256 | "domain": null 257 | }, 258 | { 259 | "name": "ssnum", 260 | "type": "esriFieldTypeDouble", 261 | "alias": "ssnum", 262 | "domain": null 263 | }, 264 | { 265 | "name": "tcdir", 266 | "type": "esriFieldTypeDouble", 267 | "alias": "tcdir", 268 | "domain": null 269 | }, 270 | { 271 | "name": "tcspd", 272 | "type": "esriFieldTypeDouble", 273 | "alias": "tcspd", 274 | "domain": null 275 | }, 276 | { 277 | "name": "datelbl", 278 | "type": "esriFieldTypeString", 279 | "alias": "datelbl", 280 | "length": 255, 281 | "domain": null 282 | }, 283 | { 284 | "name": "fldatelbl", 285 | "type": "esriFieldTypeString", 286 | "alias": "fldatelbl", 287 | "length": 255, 288 | "domain": null 289 | }, 290 | { 291 | "name": "timezone", 292 | "type": "esriFieldTypeString", 293 | "alias": "timezone", 294 | "length": 255, 295 | "domain": null 296 | }, 297 | { 298 | "name": "stormsrc", 299 | "type": "esriFieldTypeString", 300 | "alias": "stormsrc", 301 | "length": 255, 302 | "domain": null 303 | }, 304 | { 305 | "name": "url", 306 | "type": "esriFieldTypeString", 307 | "alias": "url", 308 | "length": 512, 309 | "domain": null 310 | }, 311 | { 312 | "name": "latest", 313 | "type": "esriFieldTypeSmallInteger", 314 | "alias": "latest", 315 | "domain": null 316 | }, 317 | { 318 | "name": "validtime", 319 | "type": "esriFieldTypeDate", 320 | "alias": "validtime", 321 | "length": 36, 322 | "domain": null 323 | }, 324 | { 325 | "name": "starttime", 326 | "type": "esriFieldTypeDate", 327 | "alias": "starttime", 328 | "length": 36, 329 | "domain": null 330 | }, 331 | { 332 | "name": "endtime", 333 | "type": "esriFieldTypeDate", 334 | "alias": "endtime", 335 | "length": 36, 336 | "domain": null 337 | }, 338 | { 339 | "name": "reftime", 340 | "type": "esriFieldTypeDate", 341 | "alias": "reftime", 342 | "length": 36, 343 | "domain": null 344 | }, 345 | { 346 | "name": "projmins", 347 | "type": "esriFieldTypeInteger", 348 | "alias": "projmins", 349 | "domain": null 350 | }, 351 | { 352 | "name": "desigreftime", 353 | "type": "esriFieldTypeDate", 354 | "alias": "desigreftime", 355 | "length": 36, 356 | "domain": null 357 | }, 358 | { 359 | "name": "desigprojmins", 360 | "type": "esriFieldTypeInteger", 361 | "alias": "desigprojmins", 362 | "domain": null 363 | }, 364 | { 365 | "name": "advvalidtime", 366 | "type": "esriFieldTypeString", 367 | "alias": "advvalidtime", 368 | "length": 255, 369 | "domain": null 370 | } 371 | ], 372 | "relationships": [], 373 | "canModifyLayer": false, 374 | "canScaleSymbols": false, 375 | "hasLabels": true, 376 | "capabilities": "Map,Query,Data", 377 | "maxRecordCount": 1000, 378 | "supportsStatistics": true, 379 | "supportsAdvancedQueries": true, 380 | "supportedQueryFormats": "JSON, AMF", 381 | "ownershipBasedAccessControlForFeatures": {"allowOthersToQuery": true}, 382 | "useStandardizedQueries": true 383 | } -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/chutzpahSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON schema for Chutzpah test runner settings files", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | 5 | "type": "object", 6 | "additionalProperties": true, 7 | 8 | "definitions": { 9 | "serverSettings": { 10 | "description": "Server settings let you enable to configure Chutzpah web server mode.", 11 | "type": "object", 12 | "properties": { 13 | "Enabled": { 14 | "description": "Determines if the web server mode is enabled.", 15 | "type": "boolean", 16 | "default": false 17 | }, 18 | "DefaultPort": { 19 | "description": "The default port to use. If this port is taken Chutzpah will try incrementing until it finds an available one.", 20 | "type": "number" 21 | }, 22 | "RootPath": { 23 | "description": "The root path of the server. All file paths are relative to this and should be in a directory below or equal to this. Defaults to drive root.", 24 | "type": "string" 25 | } 26 | } 27 | }, 28 | "templateOptions": { 29 | "properties": { 30 | "Mode": { 31 | "description": "The way the template is injected into the HTML page.", 32 | "enum": [ "Raw", "Script" ], 33 | "default": "Raw" 34 | }, 35 | "Id": { 36 | "description": "If in script mode what Id to place on the script tag.", 37 | "type": "string" 38 | }, 39 | "Type": { 40 | "description": "If in script mode what Type to place on script tag", 41 | "type": "string" 42 | } 43 | } 44 | }, 45 | "referenceSettings": { 46 | "properties": { 47 | "Path": { 48 | "description": "The path to either a file or a folder. If given a folder, it will be scanned recursively. This path can be relative to the location of the chutzpah.json file.", 49 | "type": "string" 50 | }, 51 | "Includes": { 52 | "description": "This is an optional array of include glob patterns. Only files matching the Include pattern will be added.", 53 | "type": "array", 54 | "items": { 55 | "type": "string" 56 | } 57 | }, 58 | "Excludes": { 59 | "description": "This is an optional array of exclude glob patterns. Only files not matching the Exclude patterns will be added.", 60 | "type": "array", 61 | "items": { 62 | "type": "string" 63 | } 64 | }, 65 | "IncludeInTestHarness": { 66 | "description": "This determines if the reference should be injected into the test harness. When referencing files like .d.ts or files that you plan to load using require.js you should set this to false. Defaults to true.", 67 | "type": "boolean", 68 | "default": true 69 | }, 70 | "IsTestFrameworkFile": { 71 | "description": "Indicated that this references should be placed directly after the test framework files in the test harness. This ensures that this file is injected into the test harness before almost all other files. Defaults to false.", 72 | "type": "boolean", 73 | "default": false 74 | }, 75 | "TemplateOptions": { 76 | "$ref": "#/definitions/templateOptions" 77 | } 78 | } 79 | }, 80 | "compilePathMap": { 81 | "properties": { 82 | "SourcePath": { 83 | "description": "The source file/directory", 84 | "type": "string" 85 | }, 86 | "OutputPath": { 87 | "description": "The file/directory that source file/directory is mapped to. Specifying a file OutputPath and a directory for SourcePath indicated the files are being concatentated into one large file", 88 | "type": "string" 89 | }, 90 | "OutputPathType": { 91 | "description": "The type (file or folder) that the output path refers to. If not specified Chutzpah will try to take a best guess by assuming it is a file if it has a .js extension", 92 | "type": "string", 93 | "enum": [ "File", "Folder" ], 94 | "default": "Folder" 95 | } 96 | } 97 | }, 98 | "testSettings": { 99 | "properties": { 100 | "Path": { 101 | "description": "The path to either a file or a folder. If given a folder, it will be scanned recursively. This path can be relative to the location of the chutzpah.json file.", 102 | "type": "string" 103 | }, 104 | "Includes": { 105 | "description": "This is an optional array of include glob patterns. Only files matching the Include pattern will be added.", 106 | "type": "array", 107 | "items": { 108 | "type": "string" 109 | } 110 | }, 111 | "Excludes": { 112 | "description": "This is an optional array of exclude glob patterns. Only files not matching the Exclude patterns will be added.", 113 | "type": "array", 114 | "items": { 115 | "type": "string" 116 | } 117 | } 118 | } 119 | }, 120 | "transformConfig": { 121 | "properties": { 122 | "Name": { 123 | "description": "The name of the transform to execute", 124 | "type": "string" 125 | }, 126 | "Path": { 127 | "description": "The file for the transform to save its output to.", 128 | "type": "string" 129 | } 130 | } 131 | }, 132 | "compileSettings": { 133 | "description": "This setting lets you describe in the Chutzpah.json file how to execute a command which can compile your source files to .js files. You tell Chutzpah what to execute and some information about what your executable does (like where to find the generated .js files). Then after running the executable Chutzpah can associate each source file with each output file to still give the nice behavior of mapping tests back to their original files.", 134 | "type": "object", 135 | "properties": { 136 | "Extensions": { 137 | "description": "The extensions of the files which are getting compiled (e.g. .ts).", 138 | "type": "array", 139 | "items": { 140 | "type": "string" 141 | } 142 | }, 143 | "ExtensionsWithNoOutput": { 144 | "description": "The extensions of files which take part in compile but have no build output. This is used for cases like TypeScript declaration files which share a .ts extension. They have .d.ts extension and are part of compilation but have no output. You must tell Chutzpah about these if you want the SkipIfUnchanged setting to work. Otherwise Chutzpah will think these are missing output.", 145 | "type": "array", 146 | "items": { 147 | "type": "string" 148 | } 149 | }, 150 | "Paths": { 151 | "description": " The collection of path mapping from source directory/file to output directory/file.", 152 | "type": "array", 153 | "items": { 154 | "$ref": "#/definitions/compilePathMap" 155 | } 156 | }, 157 | "WorkingDirectory": { 158 | "description": "This is the working directory of the process which executes the command.", 159 | "type": "string" 160 | }, 161 | "Executable": { 162 | "description": "The path to an executable which Chutzpah executes to perform the batch compilation. Chutzpah will try to resolve the path relative to the settings directory. But if can’t find the file there you must give it a full path.", 163 | "type": [ "string", "null" ], 164 | "default": null 165 | }, 166 | "Arguments": { 167 | "description": "The arguments to pass to the command.", 168 | "type": [ "string", "null" ], 169 | "default": null 170 | }, 171 | "Timeout": { 172 | "description": "How long to wait for compile to finish in milliseconds?", 173 | "type": "integer", 174 | "default": 30000 175 | }, 176 | "SkipIfUnchanged": { 177 | "description": "Skips the execution if all files Chutzpah knows about are older than all of the output files. This is defaulted to true but if you hit issues since it is possible Chutzpah might not know about all the files your compilation is using then you can turn this off. Ideally you should tell Chutzpah about these files using the references and tests settings since this setting helps Chutzpah not need to even invoke the executable if it figures out it’s not needed.", 178 | "type": "boolean", 179 | "default": true 180 | }, 181 | "Mode": { 182 | "description": "Determines how this compile setting is used. By default it is in Executable mode where it will require you provide an executable which Chutzpah will run if it sees it finds missing .js for input file. If you set this to External then Chutzpah will ignore the Executable, Arguments settings and assume you have some external process which is compiling. In this case Chutzpah will use the SourceDirectory and OutDirectory options to try to find your .js files for the input files. If it can't find them it will trace an error but still attempt to proceed.", 183 | "type": "string", 184 | "enum": [ "Executable", "External" ], 185 | "default": "External" 186 | }, 187 | "UseSourceMaps": { 188 | "description": "Configures whether .map files should be loaded (if available) to convert under-test JS line numbers to those of their original source files.", 189 | "type": "boolean", 190 | "default": false 191 | }, 192 | "IgnoreMissingFiles": { 193 | "description": "Should Chutzpah ignore files it expects to find compiled. If set to true Chutzpah will log an error otherwise it will throw", 194 | "type": "boolean", 195 | "default": false 196 | } 197 | } 198 | } 199 | }, 200 | 201 | "properties": { 202 | "Framework": { 203 | "description": "Determines what testing framework to use. This will override the other detection methods.", 204 | "type": "string", 205 | "enum": [ "qunit", "jasmine", "mocha" ] 206 | }, 207 | "FrameworkVersion": { 208 | "description": "Tells Chutzpah if it should use a different version of on of the test frameworks than the default one. Currently, the only framework this works for is Jasmine. As of the 3.1.0 release Chutzpah default to Jasmine 2.0 but if you want to use the 1.0 line for Jasmine still pass '1' for FrameworkVersion.", 209 | "type": "string" 210 | }, 211 | "References": { 212 | "description": "The references setting allows you to specify which files/folders to use/scan to find references. This is useful since it replaces the need to the /// 1 13 | calledfrom = ' ( called From '+aerrs(m.stacklevels-1,4)+' line '+Transform(aerrs(m.stacklevels-1,5))+')' 14 | Else 15 | calledfrom = '' 16 | Endif 17 | 18 | Try 19 | 20 | cerror = '' 21 | If Not Left(Ltrim(cjsonstr),1) $ '{[' And File(m.cjsonstr) 22 | cjsonstr = Filetostr(m.cjsonstr) 23 | Endif 24 | 25 | ost = Set('strictdate') 26 | Set StrictDate To 0 27 | ojson = nfjsonread2(m.cjsonstr, m.revivecollection) 28 | Set StrictDate To (m.ost) 29 | 30 | Catch To oerr1 31 | cerror = 'nfJson '+m.calledfrom+crlf+m.oerr1.Message 32 | 33 | Endtry 34 | 35 | If !Empty(m.cerror) 36 | Error m.cerror 37 | Return .Null. 38 | Endif 39 | 40 | Return Iif(Vartype(m.ojson)='O',m.ojson,.Null.) 41 | 42 | *------------------------------------------------------------------------- 43 | Function nfjsonread2(cjsonstr,revivecollection) 44 | *------------------------------------------------------------------------- 45 | 46 | Try 47 | 48 | x = 1 49 | cerror = '' 50 | 51 | * process json: 52 | 53 | cjson = Rtrim(Chrtran(m.cjsonstr,Chr(13)+Chr(9)+Chr(10),'')) 54 | pchar = Left(Ltrim(m.cjson),1) 55 | 56 | nl = Alines(aj,m.cjson,20,'{','}','"',',',':','[',']','\\') 57 | 58 | For xx = 1 To Alen(aj) 59 | If Left(Ltrim(aj(m.xx)),1) $ '{}",:[]' Or Lower(Left(Ltrim(m.aj(m.xx)),4)) $ 'true/false/null' 60 | aj(m.xx) = Ltrim(aj(m.xx)) 61 | Endif 62 | Endfor 63 | 64 | ostack = Createobject('stack') 65 | 66 | ojson = Createobject('empty') 67 | 68 | Do Case 69 | Case aj(1)='{' 70 | x = 1 71 | ostack.pushobject() 72 | procstring(m.ojson) 73 | 74 | Case aj(1) = '[' 75 | x = 0 76 | procstring(m.ojson,.T.) 77 | 78 | Otherwise 79 | Error ' expecting [{ got '+m.pchar 80 | 81 | Endcase 82 | 83 | If m.revivecollection 84 | ojson = revivecollection(m.ojson) 85 | Endif 86 | 87 | Catch To oerr 88 | 89 | strp = '' 90 | 91 | For Y = 1 To m.x 92 | strp = m.strp+aj(m.y) 93 | Endfor 94 | 95 | Do Case 96 | Case oerr.ErrorNo = 1098 97 | 98 | cerror = ' Invalid Json: '+ m.oerr.Message+crlf+' Parsing: '+Right(m.strp,80) 99 | 100 | Otherwise 101 | 102 | cerror = ' program error # '+Transform(m.oerr.ErrorNo)+crlf+m.oerr.Message+' at line: '+Transform(oerr.Lineno)+crlf+' Parsing: '+Right(m.strp,80) 103 | 104 | Endcase 105 | 106 | Endtry 107 | 108 | If !Empty(m.cerror) 109 | Error m.cerror 110 | Endif 111 | 112 | Return m.ojson 113 | 114 | *-------------------------------------------------------------------------------- 115 | Procedure procstring(obj,evalue) 116 | *-------------------------------------------------------------------------------- 117 | #Define cvalid 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_' 118 | #Define creem '_______________________________________________________________' 119 | 120 | Private rowpos,colpos,bidim,ncols,arrayname,expecting,arraylevel,vari 121 | Private expectingpropertyname,expectingvalue,objectopen 122 | 123 | expectingpropertyname = !m.evalue 124 | expectingvalue = m.evalue 125 | expecting = Iif(expectingpropertyname,'"}','') 126 | objectopen = .T. 127 | bidim = .F. 128 | colpos = 0 129 | rowpos = 0 130 | arraylevel = 0 131 | arrayname = '' 132 | vari = '' 133 | ncols = 0 134 | 135 | Do While m.objectopen 136 | 137 | x = m.x+1 138 | 139 | Do Case 140 | 141 | Case m.x > m.nl 142 | 143 | m.x = m.nl 144 | 145 | If ostack.Count > 0 146 | Error 'expecting '+m.expecting 147 | Endif 148 | 149 | Return 150 | 151 | Case aj(m.x) = '}' And '}' $ m.expecting 152 | closeobject() 153 | 154 | Case aj(x) = ']' And ']' $ m.expecting 155 | closearray() 156 | 157 | Case m.expecting = ':' 158 | If aj(m.x) = ':' 159 | expecting = '' 160 | Loop 161 | Else 162 | Error 'expecting : got '+aj(m.x) 163 | Endif 164 | 165 | Case ',' $ m.expecting 166 | 167 | Do Case 168 | Case aj(x) = ',' 169 | expecting = Iif( '[' $ m.expecting , '[' , '' ) 170 | Case Not aj(m.x) $ m.expecting 171 | Error 'expecting '+m.expecting+' got '+aj(m.x) 172 | Otherwise 173 | expecting = Strtran(m.expecting,',','') 174 | Endcase 175 | 176 | Case m.expectingpropertyname 177 | 178 | If aj(m.x) = '"' 179 | propertyname(m.obj) 180 | Else 181 | Error 'expecting "'+m.expecting+' got '+aj(m.x) 182 | Endif 183 | 184 | Case m.expectingvalue 185 | 186 | If m.expecting == '[' And m.aj(m.x) # '[' 187 | Error 'expecting [ got '+aj(m.x) 188 | Else 189 | procvalue(m.obj) 190 | Endif 191 | 192 | Endcase 193 | 194 | Enddo 195 | 196 | *----------------------------------------------------------------------------- 197 | Function anuevoel(obj,arrayname,valasig,bidim,colpos,rowpos) 198 | *----------------------------------------------------------------------------- 199 | 200 | If m.bidim 201 | 202 | colpos = m.colpos+1 203 | 204 | If colpos > m.ncols 205 | ncols = m.colpos 206 | Endif 207 | 208 | Dimension obj.&arrayname(m.rowpos,m.ncols) 209 | 210 | obj.&arrayname(m.rowpos,m.colpos) = m.valasig 211 | 212 | If Vartype(m.valasig) = 'O' 213 | procstring(obj.&arrayname(m.rowpos,m.colpos)) 214 | Endif 215 | 216 | Else 217 | 218 | rowpos = m.rowpos+1 219 | Dimension obj.&arrayname(m.rowpos) 220 | 221 | obj.&arrayname(m.rowpos) = m.valasig 222 | 223 | If Vartype(m.valasig) = 'O' 224 | procstring(obj.&arrayname(m.rowpos)) 225 | Endif 226 | 227 | Endif 228 | 229 | *----------------------------------------- 230 | Function unescunicode( cstr ) 231 | *----------------------------------------- 232 | 233 | Private All 234 | 235 | ust = '' 236 | 237 | For x = 1 To Alines(xstr,m.cstr,18,'\u','\\u') 238 | 239 | If Right(xstr(m.x),3) # '\\u' And Right(xstr(m.x),2) = '\u' 240 | 241 | ust = m.ust + Rtrim(xstr(M.x),0,'\u') 242 | 243 | dec = Val( "0x"+Left(xstr(m.x+1),4)) 244 | 245 | Ansi = left(Strconv( BinToC( m.dec , "R" ) ,6 ),1) 246 | 247 | If m.ansi # '?' 248 | ust = m.ust + m.ansi 249 | Else 250 | ust = m.ust + '&#'+Transform(m.dec)+';' 251 | Endif 252 | 253 | xstr(m.x+1) = Substr(xstr(m.x+1),5) 254 | 255 | Else 256 | 257 | ust = m.ust + xstr(m.x) 258 | 259 | Endif 260 | 261 | Endfor 262 | 263 | cstr = m.ust 264 | 265 | *----------------------------------- 266 | Function unescapecontrolc( Value ) 267 | *----------------------------------- 268 | 269 | If At('\', m.value) = 0 270 | Return 271 | Endif 272 | 273 | * unescape special characters: 274 | 275 | Private aa,elem,unesc 276 | 277 | Declare aa(1) 278 | =Alines(m.aa,m.value,18,'\\','\b','\f','\n','\r','\t','\"','\/') 279 | 280 | unesc ='' 281 | 282 | #Define sustb 'bnrt/"' 283 | #Define sustr Chr(127)+Chr(10)+Chr(13)+Chr(9)+Chr(47)+Chr(34) 284 | 285 | For Each elem In m.aa 286 | 287 | If ! m.elem == '\\' And Left(Right(m.elem,2),1) = '\' 288 | elem = Left(m.elem,Len(m.elem)-2)+Chrtran(Right(m.elem,1),sustb,sustr) 289 | Endif 290 | 291 | unesc = m.unesc+m.elem 292 | 293 | Endfor 294 | 295 | Value = m.unesc 296 | 297 | *-------------------------------------------- 298 | Procedure propertyname(obj) 299 | *-------------------------------------------- 300 | 301 | x = m.x+1 302 | vari = aj(m.x) 303 | 304 | Do While Right(aj(m.x),1) # '"' And m.x < Alen(m.aj) 305 | x=m.x+1 306 | vari = m.vari + aj(m.x) 307 | Enddo 308 | 309 | If Right(m.aj(m.x),1) # '"' 310 | Error ' expecting " got '+ m.aj(m.x) 311 | Endif 312 | 313 | vari = Rtrim(m.vari,1,'"') 314 | vari = Iif(Isalpha(m.vari),'','_')+m.vari 315 | vari = Chrtran( vari, Chrtran( vari, cvalid,'' ) , creem ) 316 | 317 | If vari == 'tabindex' 318 | vari = '_tabindex' 319 | Endif 320 | 321 | expecting = ':' 322 | expectingvalue = .T. 323 | expectingpropertyname = .F. 324 | 325 | *------------------------------------------------------------- 326 | Procedure procvalue(obj) 327 | *------------------------------------------------------------- 328 | 329 | Do Case 330 | Case aj(m.x) = '{' 331 | 332 | ostack.pushobject() 333 | 334 | If m.arraylevel = 0 335 | 336 | AddProperty(obj,m.vari,Createobject('empty')) 337 | 338 | procstring(m.obj.&vari) 339 | expectingpropertyname = .T. 340 | expecting = ',}' 341 | expectingvalue = .F. 342 | 343 | Else 344 | 345 | anuevoel(m.obj,m.arrayname,Createobject('empty'),m.bidim,@m.colpos,@m.rowpos) 346 | expectingpropertyname = .F. 347 | expecting = ',]' 348 | expectingvalue = .T. 349 | 350 | Endif 351 | 352 | Case aj(x) = '[' 353 | 354 | ostack.pusharray() 355 | 356 | Do Case 357 | 358 | Case m.arraylevel = 0 359 | 360 | arrayname = Evl(m.vari,'array') 361 | rowpos = 0 362 | colpos = 0 363 | bidim = .F. 364 | 365 | Try 366 | AddProperty(obj,(m.arrayname+'(1)'),.Null.) 367 | Catch 368 | m.arrayname = m.arrayname+'_vfpSafe_' 369 | AddProperty(obj,(m.arrayname+'(1)'),.Null.) 370 | Endtry 371 | 372 | Case m.arraylevel = 1 And !m.bidim 373 | 374 | rowpos = 1 375 | colpos = 0 376 | ncols = 1 377 | 378 | Dime obj.&arrayname(1,2) 379 | bidim = .T. 380 | 381 | Endcase 382 | 383 | arraylevel = m.arraylevel+1 384 | 385 | vari='' 386 | 387 | expecting = Iif(!m.bidim,'[]{',']') 388 | expectingvalue = .T. 389 | expectingpropertyname = .F. 390 | 391 | Otherwise 392 | 393 | isstring = aj(m.x)='"' 394 | x = m.x + Iif(m.isstring,1,0) 395 | 396 | Value = '' 397 | 398 | Do While m.x <= Alen(m.aj) 399 | Value = m.value + aj(m.x) 400 | If ( ( m.isstring And Right(aj(m.x),1) = '"' ) Or (!m.isstring And Right(aj(m.x),1) $ '}],') ) And Left(Right(aj(m.x),2),1) # '\' 401 | Exit 402 | Endif 403 | x = m.x+1 404 | Enddo 405 | 406 | closechar = Right(aj(m.x),1) 407 | 408 | Value = Left(m.value,Len(m.value)-1) 409 | 410 | Do Case 411 | 412 | Case Empty(m.value) And Not ( m.isstring And m.closechar = '"' ) 413 | Error 'Expecting value got '+m.closechar 414 | 415 | Case m.isstring 416 | If m.closechar # '"' 417 | Error 'expecting " got '+m.closechar 418 | Endif 419 | 420 | Case ostack.isobject() And Not m.closechar $ ',}' 421 | Error 'expecting ,} got '+m.closechar 422 | 423 | Case ostack.isarray() And Not m.closechar $ ',]' 424 | Error 'expecting ,] got '+m.closechar 425 | 426 | Endcase 427 | 428 | If m.isstring 429 | 430 | * don't change this lines sequence!: 431 | unescunicode(@m.value) && 1 432 | unescapecontrolc(@m.value) && 2 433 | Value = Strtran(m.value,'\\','\') && 3 434 | 435 | ** check for Json DateTime: && 2017-03-10T17:43:55 436 | * proper formatted dates with invalid values will parse as character - eg: {"today":"2017-99-01T15:99:00"} 437 | 438 | If isjsondt( m.value ) 439 | Value = jsondatetodt( m.value ) 440 | Endif 441 | 442 | Else 443 | 444 | Value = Alltrim(m.value) 445 | 446 | Do Case 447 | Case Lower(m.value) == 'null' 448 | Value = .Null. 449 | Case Lower(m.value) == 'true' Or Lower(m.value) == 'false' 450 | Value = m.value='true' 451 | 452 | Case Empty(Chrtran(m.value,'-1234567890.Ee','')) 453 | 454 | Try 455 | Local tvaln,im 456 | im = 'tvaln = '+m.value 457 | &im 458 | Value = m.tvaln 459 | badnumber = .F. 460 | Catch 461 | badnumber = .T. 462 | Endtry 463 | 464 | If badnumber 465 | Error 'bad number format: got '+aj(m.x) 466 | Endif 467 | 468 | Otherwise 469 | Error 'expecting "|number|null|true|false| got '+aj(m.x) 470 | Endcase 471 | 472 | Endif 473 | 474 | If m.arraylevel = 0 475 | 476 | AddProperty(obj,m.vari,m.value) 477 | 478 | expecting = '}' 479 | expectingvalue = .F. 480 | expectingpropertyname = .T. 481 | 482 | Else 483 | 484 | anuevoel(obj,m.arrayname,m.value,m.bidim,@m.colpos,@m.rowpos) 485 | expecting = ']' 486 | expectingvalue = .T. 487 | expectingpropertyname = .F. 488 | 489 | Endif 490 | 491 | expecting = Iif(m.isstring,',','')+m.expecting 492 | 493 | Do Case 494 | Case m.closechar = ']' 495 | closearray() 496 | Case m.closechar = '}' 497 | closeobject() 498 | Endcase 499 | 500 | Endcase 501 | 502 | *------------------------------ 503 | Function closearray() 504 | *------------------------------ 505 | 506 | If ostack.Pop() # 'A' 507 | Error 'unexpected ] ' 508 | Endif 509 | 510 | If m.arraylevel = 0 511 | Error 'unexpected ] ' 512 | Endif 513 | 514 | arraylevel = m.arraylevel-1 515 | 516 | If m.arraylevel = 0 517 | 518 | arrayname = '' 519 | rowpos = 0 520 | colpos = 0 521 | 522 | expecting = Iif(ostack.isobject(),',}','') 523 | expectingpropertyname = .T. 524 | expectingvalue = .F. 525 | 526 | Else 527 | 528 | If m.bidim 529 | rowpos = m.rowpos+1 530 | colpos = 0 531 | expecting = ',][' 532 | Else 533 | expecting = ',]' 534 | Endif 535 | 536 | expectingvalue = .T. 537 | expectingpropertyname = .F. 538 | 539 | Endif 540 | 541 | *------------------------------------- 542 | Procedure closeobject 543 | *------------------------------------- 544 | 545 | If ostack.Pop() # 'O' 546 | Error 'unexpected }' 547 | Endif 548 | 549 | If m.arraylevel = 0 550 | expecting = ',}' 551 | expectingvalue = .F. 552 | expectingpropertyname = .T. 553 | objectopen = .F. 554 | Else 555 | expecting = ',]' 556 | expectingvalue = .T. 557 | expectingpropertyname = .F. 558 | Endif 559 | 560 | *---------------------------------------------- 561 | Function revivecollection( o ) 562 | *---------------------------------------------- 563 | 564 | Private All 565 | 566 | oconv = Createobject('empty') 567 | 568 | nprop = Amembers(elem,m.o,0,'U') 569 | 570 | For x = 1 To m.nprop 571 | 572 | estavar = m.elem(x) 573 | 574 | esarray = .F. 575 | escoleccion = Type('m.o.'+m.estavar) = 'O' And Right( m.estavar , 14 ) $ '_KV_COLLECTION,_KL_COLLECTION' And Type( 'm.o.'+m.estavar+'.collectionitems',1) = 'A' 576 | 577 | Do Case 578 | Case m.escoleccion 579 | 580 | estaprop = Createobject('collection') 581 | 582 | tv = m.o.&estavar 583 | 584 | m.keyvalcoll = Right( m.estavar , 14 ) = '_KV_COLLECTION' 585 | 586 | If Not ( Alen(m.tv.collectionItems) = 1 And Isnull( m.tv.collectionItems ) ) 587 | 588 | For T = 1 To Alen(m.tv.collectionItems) 589 | 590 | If m.keyvalcoll 591 | esteval = m.tv.collectionItems(m.t).Value 592 | Else 593 | esteval = m.tv.collectionItems(m.t) 594 | Endif 595 | 596 | If Vartype(m.esteval) = 'O' Or Type('esteVal',1) = 'A' 597 | esteval = revivecollection(m.esteval) 598 | Endif 599 | 600 | If m.keyvalcoll 601 | estaprop.Add(esteval,m.tv.collectionItems(m.t).Key) 602 | Else 603 | estaprop.Add(m.esteval) 604 | Endif 605 | 606 | Endfor 607 | 608 | Endif 609 | 610 | Case Type('m.o.'+m.estavar,1) = 'A' 611 | 612 | esarray = .T. 613 | 614 | For T = 1 To Alen(m.o.&estavar) 615 | 616 | Dimension &estavar(m.t) 617 | 618 | If Type('m.o.&estaVar(m.T)') = 'O' 619 | &estavar(m.t) = revivecollection(m.o.&estavar(m.t)) 620 | Else 621 | &estavar(m.t) = m.o.&estavar(m.t) 622 | Endif 623 | 624 | Endfor 625 | 626 | Case Type('m.o.'+estavar) = 'O' 627 | estaprop = revivecollection(m.o.&estavar) 628 | 629 | Otherwise 630 | estaprop = m.o.&estavar 631 | 632 | Endcase 633 | 634 | estavar = Strtran( m.estavar,'_KV_COLLECTION', '' ) 635 | estavar = Strtran( m.estavar, '_KL_COLLECTION', '' ) 636 | 637 | Do Case 638 | Case m.escoleccion 639 | AddProperty(m.oconv,m.estavar,m.estaprop) 640 | Case m.esarray 641 | AddProperty(m.oconv,m.estavar+'(1)') 642 | Acopy(&estavar,m.oconv.&estavar) 643 | Otherwise 644 | AddProperty(m.oconv,m.estavar,m.estaprop) 645 | Endcase 646 | 647 | Endfor 648 | 649 | Try 650 | retcollection = m.oconv.Collection.BaseClass = 'Collection' 651 | Catch 652 | retcollection = .F. 653 | Endtry 654 | 655 | If m.retcollection 656 | Return m.oconv.Collection 657 | Else 658 | Return m.oconv 659 | Endif 660 | 661 | *---------------------------------- 662 | Function isjsondt( cstr ) 663 | *---------------------------------- 664 | 665 | cstr = Rtrim(m.cstr,1,'Z') 666 | 667 | Return Iif( Len(m.cstr) = 19 ; 668 | and Len(Chrtran(m.cstr,'01234567890:T-','')) = 0 ; 669 | and Substr(m.cstr,5,1) = '-' ; 670 | and Substr(m.cstr,8,1) = '-' ; 671 | and Substr(m.cstr,11,1) = 'T' ; 672 | and Substr(m.cstr,14,1) = ':' ; 673 | and Substr(m.cstr,17,1) = ':' ; 674 | and Occurs('T',m.cstr) = 1 ; 675 | and Occurs('-',m.cstr) = 2 ; 676 | and Occurs(':',m.cstr) = 2 ,.T.,.F. ) 677 | 678 | *----------------------------------------------------- 679 | Procedure jsondatetodt( cjsondate ) 680 | *----------------------------------------------------- 681 | 682 | cjsondate = Rtrim(m.cjsondate,1,'Z') 683 | 684 | If m.cjsondate = '0000-00-00T00:00:00' 685 | 686 | Return {} 687 | 688 | Else 689 | 690 | cret = Eval('{^'+Rtrim(m.cjsondate,1,"T00:00:00")+'}') 691 | 692 | If !Empty(m.cret) 693 | Return m.cret 694 | Else 695 | Error 'Invalid date '+cjsondate 696 | Endif 697 | 698 | Endif 699 | 700 | ****************************************** 701 | Define Class Stack As Collection 702 | ****************************************** 703 | 704 | *--------------------------- 705 | Function pushobject() 706 | *--------------------------- 707 | This.Add('O') 708 | 709 | *--------------------------- 710 | Function pusharray() 711 | *--------------------------- 712 | This.Add('A') 713 | 714 | *-------------------------------------- 715 | Function isobject() 716 | *-------------------------------------- 717 | Return This.Count > 0 And This.Item( This.Count ) = 'O' 718 | 719 | *-------------------------------------- 720 | Function isarray() 721 | *-------------------------------------- 722 | Return This.Count > 0 And This.Item( This.Count ) = 'A' 723 | 724 | *---------------------------- 725 | Function Pop() 726 | *---------------------------- 727 | cret = This.Item( This.Count ) 728 | This.Remove( This.Count ) 729 | Return m.cret 730 | 731 | ****************************************** 732 | Enddefine 733 | ****************************************** 734 | -------------------------------------------------------------------------------- /nfJson/Tests/jsonSamples/googleExtensionSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "JSON schema for Google Chrome extension manifest files", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | 5 | "type": "object", 6 | "additionalProperties": true, 7 | "required": [ "manifest_version", "name", "version" ], 8 | 9 | "properties": { 10 | "manifest_version": { 11 | "type": "number", 12 | "description": "One integer specifying the version of the manifest file format your package requires.", 13 | "enum": [ 2 ], 14 | "minimum": 2, 15 | "maximum": 2 16 | }, 17 | "name": { 18 | "type": "string", 19 | "description": "The name of the extension", 20 | "maxLength": 45 21 | }, 22 | "version": { 23 | "description": "One to four dot-separated integers identifying the version of this extension.", 24 | "$ref": "#/definitions/version_string" 25 | }, 26 | "default_locale": { 27 | "type": "string", 28 | "description": "Specifies the subdirectory of _locales that contains the default strings for this extension.", 29 | "default": "en" 30 | }, 31 | "description": { 32 | "type": "string", 33 | "description": "A plain text description of the extension", 34 | "maxLength": 132 35 | }, 36 | "icons": { 37 | "type": "object", 38 | "description": "One or more icons that represent the extension, app, or theme. Recommended format: PNG; also BMP, GIF, ICO, JPEG.", 39 | "minProperties": 1, 40 | "properties": { 41 | "16": { 42 | "$ref": "#/definitions/icon", 43 | "description": "Used as the favicon for an extension's pages and infobar." 44 | }, 45 | "48": { 46 | "$ref": "#/definitions/icon", 47 | "description": "Used on the extension management page (chrome://extensions)." 48 | }, 49 | "128": { 50 | "$ref": "#/definitions/icon", 51 | "description": "Used during installation and in the Chrome Web Store." 52 | }, 53 | "256": { 54 | "$ref": "#/definitions/icon", 55 | "description": "Used during installation and in the Chrome Web Store." 56 | } 57 | } 58 | }, 59 | "browser_action": { 60 | "$ref": "#/definitions/action", 61 | "description": "Use browser actions to put icons in the main Google Chrome toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup." 62 | }, 63 | "page_action": { 64 | "$ref": "#/definitions/action", 65 | "description": "Use the chrome.pageAction API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages." 66 | }, 67 | "background": { 68 | "type": "object", 69 | "description": "The background page is an HTML page that runs in the extension process. It exists for the lifetime of your extension, and only one instance of it at a time is active.", 70 | "properties": { 71 | "persistent": { 72 | "type": "boolean", 73 | "description": "When false, makes the background page an event page (loaded only when needed).", 74 | "default": true 75 | }, 76 | "page": { 77 | "$ref": "#/definitions/page", 78 | "description": "Specify the HTML of the background page.", 79 | "default": "background.html" 80 | }, 81 | "scripts": { 82 | "$ref": "#/definitions/scripts", 83 | "description": "A background page will be generated by the extension system that includes each of the files listed in the scripts property.", 84 | "default": [ "background.js" ] 85 | } 86 | }, 87 | "dependencies": { 88 | "page": { "not": { "required": [ "scripts" ] } }, 89 | "scripts": { "not": { "required": [ "page" ] } } 90 | } 91 | }, 92 | "chrome_url_overrides": { 93 | "type": "object", 94 | "description": "Override pages are a way to substitute an HTML file from your extension for a page that Google Chrome normally provides.", 95 | "additionalProperties": false, 96 | "maxProperties": 1, 97 | "properties": { 98 | "bookmarks": { 99 | "$ref": "#/definitions/page", 100 | "description": "The page that appears when the user chooses the Bookmark Manager menu item from the Chrome menu or, on Mac, the Bookmark Manager item from the Bookmarks menu. You can also get to this page by entering the URL chrome://bookmarks.", 101 | "default": "bookmarks.html" 102 | }, 103 | "history": { 104 | "$ref": "#/definitions/page", 105 | "description": "The page that appears when the user chooses the History menu item from the Chrome menu or, on Mac, the Show Full History item from the History menu. You can also get to this page by entering the URL chrome://history.", 106 | "default": "history.html" 107 | }, 108 | "newtab": { 109 | "$ref": "#/definitions/page", 110 | "description": "The page that appears when the user creates a new tab or window. You can also get to this page by entering the URL chrome://newtab.", 111 | "default": "newtab.html" 112 | } 113 | } 114 | }, 115 | "commands": { 116 | "type": "object", 117 | "description": "Use the commands API to add keyboard shortcuts that trigger actions in your extension, for example, an action to open the browser action or send a command to the extension.", 118 | "patternProperties": { 119 | ".*": { "$ref": "#/definitions/command" }, 120 | "^_execute_browser_action$": { "$ref": "#/definitions/command" }, 121 | "^_execute_page_action$": { "$ref": "#/definitions/command" } 122 | } 123 | }, 124 | "content_scripts": { 125 | "type": "array", 126 | "description": "Content scripts are JavaScript files that run in the context of web pages.", 127 | "minItems": 1, 128 | "uniqueItems": true, 129 | "items": { 130 | "type": "object", 131 | "required": [ "matches" ], 132 | "additionalProperties": false, 133 | "properties": { 134 | "matches": { 135 | "type": "array", 136 | "description": "Specifies which pages this content script will be injected into.", 137 | "minItems": 1, 138 | "uniqueItems": true, 139 | "items": { "$ref": "#/definitions/match_pattern" } 140 | }, 141 | "exclude_matches": { 142 | "type": "array", 143 | "description": "Excludes pages that this content script would otherwise be injected into.", 144 | "uniqueItems": true, 145 | "items": { "$ref": "#/definitions/match_pattern" } 146 | }, 147 | "css": { 148 | "type": "array", 149 | "description": "The list of CSS files to be injected into matching pages. These are injected in the order they appear in this array, before any DOM is constructed or displayed for the page.", 150 | "uniqueItems": true, 151 | "items": { "$ref": "#/definitions/uri" } 152 | }, 153 | "js": { 154 | "$ref": "#/definitions/scripts", 155 | "description": "The list of JavaScript files to be injected into matching pages. These are injected in the order they appear in this array." 156 | }, 157 | "run_at": { 158 | "type": "string", 159 | "description": "Controls when the files in js are injected.", 160 | "enum": [ "document_start", "document_end", "document_idle" ], 161 | "default": "document_idle" 162 | }, 163 | "all_frames": { 164 | "type": "boolean", 165 | "description": "Controls whether the content script runs in all frames of the matching page, or only the top frame.", 166 | "default": false 167 | }, 168 | "include_globs": { 169 | "type": "array", 170 | "description": "Applied after matches to include only those URLs that also match this glob. Intended to emulate the @include Greasemonkey keyword.", 171 | "uniqueItems": true, 172 | "items": { "$ref": "#/definitions/glob_pattern" } 173 | }, 174 | "exclude_globs": { 175 | "type": "array", 176 | "description": "Applied after matches to exclude URLs that match this glob. Intended to emulate the @exclude Greasemonkey keyword.", 177 | "uniqueItems": true, 178 | "items": { "$ref": "#/definitions/glob_pattern" } 179 | } 180 | } 181 | } 182 | }, 183 | "content_security_policy": { 184 | "$ref": "#/definitions/content_security_policy" 185 | }, 186 | "devtools_page": { 187 | "$ref": "#/definitions/page", 188 | "description": "A DevTools extension adds functionality to the Chrome DevTools. It can add new UI panels and sidebars, interact with the inspected page, get information about network requests, and more." 189 | }, 190 | "externally_connectable": { 191 | "type": "object", 192 | "description": "Declares which extensions, apps, and web pages can connect to your extension via runtime.connect and runtime.sendMessage.", 193 | "additionalProperties": false 194 | }, 195 | "file_browser_handlers": { 196 | "type": "array", 197 | "description": "You can use this API to enable users to upload files to your website.", 198 | "minItems": 1, 199 | "items": { 200 | "type": "object", 201 | "required": [ "id", "default_title", "file_filters" ], 202 | "additionalProperties": false, 203 | "properties": { 204 | "id": { 205 | "type": "string", 206 | "description": "Used by event handling code to differentiate between multiple file handlers" 207 | }, 208 | "default_title": { 209 | "type": "string", 210 | "description": "What the button will display." 211 | }, 212 | "file_filters": { 213 | "type": "array", 214 | "description": "Filetypes to match.", 215 | "minItems": 1, 216 | "items": { 217 | "type": "string" 218 | } 219 | } 220 | } 221 | } 222 | }, 223 | "homepage_url": { 224 | "$ref": "#/definitions/uri", 225 | "description": "The URL of the homepage for this extension." 226 | }, 227 | "incognito": { 228 | "type": "string", 229 | "description": "Specify how this extension will behave if allowed to run in incognito mode.", 230 | "enum": [ "spanning", "split" ], 231 | "default": "spanning" 232 | }, 233 | 234 | "input_components": { 235 | "type": "array", 236 | "description": "Allows your extension to handle keystrokes, set the composition, and manage the candidate window.", 237 | "items": { 238 | "type": "object", 239 | "required": [ "name", "type", "id", "description", "language", "layouts" ], 240 | "additionalProperties": false, 241 | "properties": { 242 | "name": { 243 | "type": "string" 244 | }, 245 | "type": { 246 | "type": "string" 247 | }, 248 | "id": { 249 | "type": "string" 250 | }, 251 | "description": { 252 | "type": "string" 253 | }, 254 | "language": { 255 | "type": "string" 256 | }, 257 | "layouts": { 258 | "type": "array" 259 | } 260 | } 261 | } 262 | }, 263 | "key": { 264 | "type": "string", 265 | "description": "This value can be used to control the unique ID of an extension, app, or theme when it is loaded during development." 266 | }, 267 | "minimum_chrome_version": { 268 | "$ref": "#/definitions/version_string", 269 | "description": "The version of Chrome that your extension, app, or theme requires, if any." 270 | }, 271 | "nacl_modules": { 272 | "type": "array", 273 | "description": "One or more mappings from MIME types to the Native Client module that handles each type.", 274 | "minItems": 1, 275 | "uniqueItems": true, 276 | "items": { 277 | "type": "object", 278 | "required": [ "path", "mime_type" ], 279 | "additionalProperties": false, 280 | "properties": { 281 | "path": { 282 | "$ref": "#/definitions/uri", 283 | "description": "The location of a Native Client manifest (a .nmf file) within the extension directory." 284 | }, 285 | "mime_type": { 286 | "$ref": "#/definitions/mime_type", 287 | "description": "The MIME type for which the Native Client module will be registered as content handler." 288 | } 289 | } 290 | } 291 | }, 292 | "oauth2": { 293 | "type": "object", 294 | "description": "Use the Chrome Identity API to authenticate users: the getAuthToken for users logged into their Google Account and the launchWebAuthFlow for users logged into a non-Google account.", 295 | "required": [ "client_id", "scopes" ], 296 | "additionalProperties": false, 297 | "properties": { 298 | "client_id": { 299 | "type": "string", 300 | "description": "You need to register your app in the Google APIs Console to get the client ID." 301 | }, 302 | "scopes": { 303 | "type": "array", 304 | "minItems": 1, 305 | "items": { 306 | "type": "string" 307 | } 308 | } 309 | } 310 | }, 311 | "offline_enabled": { 312 | "type": "boolean", 313 | "description": "Whether the app or extension is expected to work offline. When Chrome detects that it is offline, apps with this field set to true will be highlighted on the New Tab page." 314 | }, 315 | "omnibox": { 316 | "type": "object", 317 | "description": "The omnibox API allows you to register a keyword with Google Chrome's address bar, which is also known as the omnibox.", 318 | "required": [ "keyword" ], 319 | "additionalProperties": false, 320 | "properties": { 321 | "keyword": { 322 | "type": "string", 323 | "description": "The keyward that will trigger your extension." 324 | } 325 | } 326 | }, 327 | "optional_permissions": { 328 | "$ref": "#/definitions/permissions", 329 | "description": "Use the chrome.permissions API to request declared optional permissions at run time rather than install time, so users understand why the permissions are needed and grant only those that are necessary." 330 | }, 331 | "options_page": { 332 | "$ref": "#/definitions/page", 333 | "description": "To allow users to customize the behavior of your extension, you may wish to provide an options page. If you do, a link to it will be provided from the extensions management page at chrome://extensions. Clicking the Options link opens a new tab pointing at your options page.", 334 | "default": "options.html" 335 | }, 336 | "options_ui": { 337 | "type": "object", 338 | "description": "To allow users to customize the behavior of your extension, you may wish to provide an options page. If you do, an Options link will be shown on the extensions management page at chrome://extensions which opens a dialogue containing your options page.", 339 | "required": ["page"], 340 | "properties": { 341 | "page": { 342 | "type": "string", 343 | "description": "The path to your options page, relative to your extension's root." 344 | }, 345 | "chrome_style": { 346 | "type": "boolean", 347 | "default": true, 348 | "description": "If true, a Chrome user agent stylesheet will be applied to your options page. The default value is false, but we recommend you enable it for a consistent UI with Chrome." 349 | }, 350 | "open_in_tab": { 351 | "type": "boolean", 352 | "default": false, 353 | "description": "If true, your extension's options page will be opened in a new tab rather than embedded in chrome://extensions. The default is false, and we recommend that you don't change it. This is only useful to delay the inevitable deprecation of the old options UI! It will be removed soon, so try not to use it. It will break." 354 | } 355 | } 356 | }, 357 | "permissions": { 358 | "$ref": "#/definitions/permissions", 359 | "description": "Permissions help to limit damage if your extension or app is compromised by malware. Some permissions are also displayed to users before installation, as detailed in Permission Warnings." 360 | }, 361 | "requirements": { 362 | "type": "object", 363 | "description": "Technologies required by the app or extension. Hosting sites such as the Chrome Web Store may use this list to dissuade users from installing apps or extensions that will not work on their computer.", 364 | "additionalProperties": false, 365 | "properties": { 366 | "3D": { 367 | "type": "object", 368 | "description": "The '3D' requirement denotes GPU hardware acceleration.", 369 | "required": [ "features" ], 370 | "additionalProperties": false, 371 | "properties": { 372 | "features": { 373 | "type": "array", 374 | "description": "List of the 3D-related features your app requires.", 375 | "minItems": 1, 376 | "uniqueItems": true, 377 | "items": { 378 | "type": "string", 379 | "enum": [ "webgl" ] 380 | } 381 | } 382 | } 383 | }, 384 | "plugins": { 385 | "type": "object", 386 | "description": "Indicates if an app or extension requires NPAPI to run. This requirement is enabled by default when the manifest includes the 'plugins' field.", 387 | "required": [ "npapi" ], 388 | "additionalProperties": false, 389 | "properties": { 390 | "npapi": { 391 | "type": "boolean", 392 | "default": true 393 | } 394 | } 395 | } 396 | } 397 | }, 398 | "sandbox": { 399 | "type": "object", 400 | "description": "Defines an collection of app or extension pages that are to be served in a sandboxed unique origin, and optionally a Content Security Policy to use with them.", 401 | "required": [ "pages" ], 402 | "additionalProperties": false, 403 | "properties": { 404 | "pages": { 405 | "type": "array", 406 | "minItems": 1, 407 | "uniqueItems": true, 408 | "items": { "$ref": "#/definitions/page" } 409 | }, 410 | "content_security_policy": { 411 | "$ref": "#/definitions/content_security_policy", 412 | "default": "sandbox allow-scripts allow-forms" 413 | } 414 | } 415 | }, 416 | "short_name": { 417 | "type": "string", 418 | "description": "The short name is typically used where there is insufficient space to display the full name.", 419 | "maxLength": 12 420 | }, 421 | "update_url": { 422 | "$ref": "#/definitions/uri", 423 | "description": "If you publish using the Chrome Developer Dashboard, ignore this field. If you host your own extension or app: URL to an update manifest XML file." 424 | }, 425 | "tts_engine": { 426 | "type": "object", 427 | "description": "Register itself as a speech engine.", 428 | "required": [ "voices" ], 429 | "additionalProperties": false, 430 | "properties": { 431 | "voices": { 432 | "type": "array", 433 | "description": "Voices the extension can synthesize.", 434 | "minItems": 1, 435 | "uniqueItems": true, 436 | "items": { 437 | "type": "object", 438 | "required": [ "voice_name", "event_types" ], 439 | "additionalProperties": false, 440 | "properties": { 441 | "voice_name": { 442 | "type": "string", 443 | "description": "Identifies the name of the voice and the engine used." 444 | }, 445 | "lang": { 446 | "type": "string", 447 | "description": "Almost always, a voice can synthesize speech in just a single language. When an engine supports more than one language, it can easily register a separate voice for each language." 448 | }, 449 | "gender": { 450 | "type": "string", 451 | "description": "If your voice corresponds to a male or female voice, you can use this parameter to help clients choose the most appropriate voice for their application." 452 | }, 453 | "event_types": { 454 | "type": "array", 455 | "description": "Events sent to update the client on the progress of speech synthesis.", 456 | "minItems": 1, 457 | "uniqueItems": true, 458 | "items": { 459 | "type": "string", 460 | "description": "", 461 | "enum": [ "start", "word", "sentence", "marker", "end", "error" ] 462 | } 463 | } 464 | } 465 | } 466 | } 467 | } 468 | }, 469 | "version_name": { 470 | "type": "string", 471 | "description": "In addition to the version field, which is used for update purposes, version_name can be set to a descriptive version string and will be used for display purposes if present." 472 | }, 473 | "web_accessible_resources": { 474 | "type": "array", 475 | "description": "An array of strings specifying the paths (relative to the package root) of packaged resources that are expected to be usable in the context of a web page.", 476 | "minItems": 1, 477 | "uniqueItems": true, 478 | "items": { 479 | "$ref": "#/definitions/uri" 480 | } 481 | }, 482 | "chrome_settings_overrides": { }, 483 | "content_pack": { }, 484 | "current_locale": { }, 485 | "import": { }, 486 | "platforms": { }, 487 | "signature": { }, 488 | "spellcheck": { }, 489 | "storage": { }, 490 | "system_indicator": { } 491 | }, 492 | "dependencies": { 493 | "page_action": { "not": { "required": [ "browser_action" ] } }, 494 | "browser_action": { "not": { "required": [ "page_action" ] } }, 495 | "content_scripts": { "not": { "required": [ "script_badge" ] } }, 496 | "script_badge": { "not": { "required": [ "content_scripts" ] } } 497 | }, 498 | "definitions": { 499 | "action": { 500 | "type": "object", 501 | "properties": { 502 | "default_title": { 503 | "type": "string", 504 | "description": "Tooltip for the main toolbar icon." 505 | }, 506 | "default_popup": { 507 | "$ref": "#/definitions/uri", 508 | "description": "The popup appears when the user clicks the icon." 509 | }, 510 | "default_icon": { 511 | "anyOf": [ 512 | { 513 | "type": "string", 514 | "description": "FIXME: String form is deprecated." 515 | }, 516 | { 517 | "type": "object", 518 | "description": "Icon for the main toolbar.", 519 | "properties": { 520 | "19": { "$ref": "#/definitions/icon" }, 521 | "38": { "$ref": "#/definitions/icon" } 522 | } 523 | } 524 | ] 525 | } 526 | }, 527 | "dependencies": { 528 | "name": { "not": { "required": [ "name" ] } }, 529 | "icons": { "not": { "required": [ "icons" ] } }, 530 | "popup": { "not": { "required": [ "popup" ] } } 531 | } 532 | }, 533 | "command": { 534 | "type": "object", 535 | "additionalProperties": false, 536 | "properties": { 537 | "description": { 538 | "type": "string" 539 | }, 540 | "suggested_key": { 541 | "type": "object", 542 | "additionalProperties": false, 543 | "patternProperties": { 544 | "^(default|mac|windows|linux|chromeos)$": { 545 | "type": "string", 546 | "pattern": "^(Ctrl|Command|MacCtrl|Alt|Option)\\+(Shift\\+)?[A-Z]" 547 | } 548 | } 549 | } 550 | } 551 | }, 552 | 553 | "content_security_policy": { 554 | "type": "string", 555 | "description": "This introduces some fairly strict policies that will make extensions more secure by default, and provides you with the ability to create and enforce rules governing the types of content that can be loaded and executed by your extensions and applications.", 556 | "format": "content-security-policy", 557 | "default": "script-src 'self'; object-src 'self'" 558 | }, 559 | "glob_pattern": { 560 | "type": "string", 561 | "format": "glob-pattern" 562 | }, 563 | "icon": { 564 | "$ref": "#/definitions/uri" 565 | }, 566 | "match_pattern": { 567 | "type": "string", 568 | "format": "match-pattern", 569 | "pattern": "^((\\*|http|https|file|ftp|chrome-extension):\\/\\/(\\*|\\*\\.[^\\/\\*]+|[^\\/\\*]+)?(\\/.*))|$" 570 | }, 571 | "mime_type": { 572 | "type": "string", 573 | "format": "mime-type", 574 | "pattern": "^(?:application|audio|image|message|model|multipart|text|video)\\/[-+.\\w]+$" 575 | }, 576 | "page": { 577 | "$ref": "#/definitions/uri" 578 | }, 579 | "permissions": { 580 | "type": "array", 581 | "uniqueItems": true, 582 | "items": { 583 | "type": "string", 584 | "format": "permission" 585 | } 586 | }, 587 | "scripts": { 588 | "type": "array", 589 | "minItems": 1, 590 | "uniqueItems": true, 591 | "items": { 592 | "$ref": "#/definitions/uri" 593 | } 594 | }, 595 | "uri": { 596 | "type": "string", 597 | "format": "uri" 598 | }, 599 | "version_string": { 600 | "type": "string", 601 | "pattern": "^(?:\\d{1,5}\\.){0,3}\\d{1,5}$" 602 | } 603 | } 604 | } 605 | --------------------------------------------------------------------------------