Through LotusScript I am consuming a webpage that returns json values and I have been unable to find any library out there for lotusscript, other than ls.snapps.JSONReader from openntf. It works, but documentation is limited. I am having trouble reading a nested array in the value list. I was able to get it to work in java using the ibm.common.utils.... library, but was having trouble with mac client and another library (javax.swing.*) so I switched to LotusScript.
I am hoping someone else has experience with the ls.snapps.JSONReader library, or maybe a different idea on how to do this. Here is the sample:
to read it I use
Dim jsonReader As jsonReader
Dim vResults As Variant
Dim vPieces As Variant
Dim sJSON As string
sJson= |{ "colorsArray":[{
"red":"#f00",
"green":"#0f0",
"blue":"#00f",
"cyan":"#0ff",
"magenta":"#f0f",
"yellow":"#ff0",
"black":"#000"
}
]
}|
Set jsonReader = New JsonReader
Set vResults = jsonReader.parse(sJson)
vPieces = vResults.items
I have no trouble when I set a single level object such as:
sJSON = |{"a":"a4255524","a24":true,"ax":"WER!!","b":"Some text"}|
I use the getItemValue method
msgbox vResults.getitemValue("a24")
will return a 'true' value
Has anyone used this JSON parser and can you give me some advice on how to get the data out?
UPDATE and interim solution:
To get json values I had to do one of two things:
use ls Replace functions to extract the single dimension data (ie, remove {"colorsArray":[ on the left side and ]} on the right side., then use snapps json reader.
I created a java library using the SBT JSON libs and called it from LotusScript. Paul Bastide put a good writeup on using the java lib http://bastide.org/2014/03/15/using-the-ibm-social-business-toolkit-to-process-json-objects/
Here is the java code:
import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.sbt.services.client.base.datahandlers.JsonDataHandler;
import lotus.domino.*;
public class GetJSON extends AgentBase {
public static String pJSON( String jData, String jEntry, String jValue ) {
String result2="";
try {
System.out.println("data: " + jData + "\n" + "\n" + "jEntry & jValue: " + jEntry + ", " + jValue);
// print to debug console
// System.out.println("jData: " + jData);
JsonJavaObject jsonObject = (JsonJavaObject) JsonParser.fromJson(JsonJavaFactory.instanceEx, jData );
JsonDataHandler handler = new JsonDataHandler();
handler.setData(jsonObject);
JsonJavaObject entryJson=handler.getEntry(jEntry);
result2=entryJson.getAsString(jValue);
} catch (Exception e) {
System.out.println("Error: " + e);
e.printStackTrace();
result2="";
}
return result2; }
}
and I call it from LotusScript as follows:
Option Public
Option Declare
Use "($getJson)"
UseLSX "*javacon"
Dim mySession As JavaSession
Dim myClass As JavaClass
Dim getJson As JavaObject
result = ""
'....
'add vars here
'....
Set mySession = New JavaSession()
Set myClass = mySession.GetClass("GetJSON")
Set GetJson = myClass.CreateObject()
MsgBox GetJson.pJSON( result2, "colorsArray", "red" )
IMPORTANT NOTE on the above, in the array string I had to remove the brackets [ and ] because I was getting an SBT array incompatibility error in java. I think by doing that it may have turned it into a single level object, but if you look at Paul's example from above URL, you'll see he doesn't add them to his example either.
I would rather do this in all Java or all LotusScript, and will probably use the modified json string with snapps, just looking for a better solution.
Here is the working code for your JSON string. Try it.
Dim jsonReader As JSONReader
Dim vResults As Variant
Dim vPieces As Variant
Dim sJSON As String
sJson= |{ "colorsArray":[{
"red":"#f00",
"green":"#0f0",
"blue":"#00f",
"cyan":"#0ff",
"magenta":"#f0f",
"yellow":"#ff0",
"black":"#000"
}
]}|
Set jsonReader = New JSONReader
Set vResults = jsonReader.parse(sJson)
Set vResultData = vResults.GetItemValue("colorsArray")
Forall vResult In vResultData.Items
Msgbox Cstr(vResult.GetItemValue("red"))
Msgbox Cstr(vResult.GetItemValue("green"))
Msgbox Cstr(vResult.GetItemValue("blue"))
Msgbox Cstr(vResult.GetItemValue("cyan"))
Msgbox Cstr(vResult.GetItemValue("magenta"))
Msgbox Cstr(vResult.GetItemValue("yellow"))
Msgbox Cstr(vResult.GetItemValue("black"))
End Forall
Related
I've been wrestling with this problem for a while. My problem is that I have a bunch of JSON data and I want to represent it as objects.
Arrays are problematic.
I create a class module such as FancyCat with a public Name as String for its name.
Then I can set this with
Dim MyFancyCat as FancyCat
Set MyFancyCat = new FancyCat
FancyCat.Name = JSONData("Name")
I've typed that from memory but I think it's correct. Anyhoo, it works fine.
The problem is that a fancy cat has several pairs of socks. The number of socks is variable.
In vba you cannot for some reason have a public array. So this code is illegal:
public Socks() as FancySock 'Illegal
Looking on SO I found two solutions, one, to make it private and use a property to access it, and the other, to declare it as Variant and then stick an array into it later.
My approach to populating this array, is to examine the JSON array to get the Count, and then to ReDim the array to match and then populate it.
The problem is my ReDim statement refuses to work.
It seems I cannot redim a property, I get an error. And I also get an error trying to redim the public variant field. My ReDim works OK if I declare a local array and redim it, so potentially I could do that and then assign it to the property... but it just seems bizarre that I can't redim it directly.
Any idea why it's not working?
With the Variant approach above my code is:
ReDim MyFancyCat.Socks(socksLength) As FancySocks
And in the FancyCat class module:
public Socks As Variant
I get Method or Data Member Not Found.
The error for the other approach was different but I rejigged all my code to try the second approach so I am not sure what it was.
Edit: I'm gonna explain what I am trying to do a bit more clearly. I have some JSON data coming in, and I want to store it as an object hierarchy.
In C# I would do this (pseudo code without linq shortcuts):
var myData = ReadJsonData(); // Produces a kind of dictionary
var myFancyCat = new FancyCat();
myFancyCat.Name = myData["Name"];
myFancyCat.Age = myData["Age"];
myFancyCat.Socks = new List<FancySock>();
foreach (var sock in myData["Socks"])
{
myFancyCat.Socks.Add(sock);
}
In excel I want to do the same thing.
So I make a class module for FancyCat and FancySock and give FancyCat public members for Name, Age etc but then I also want an array of socks that my cat owns. I wanted to do this with strongly typed references, e.g. my c# code above I can do:
myFancyCat.Socks[0].Colour // Intellisense works, shows colour as a property
However it seems in excel you can't have publicly declared arrays. So you can get around this according to the comments by declaring it as variant and then sticking an array in anyway, but you would lose the intellisense. Or you can use a get/let property which kinda works but is more fiddly as it seems you can't actually expose an array using a get/let you have to have it take an index and expose elements individually.
So at this point I am thinking forget the strongly typed it's not happening, perhaps use a collection?
The FancySock class may have further nested arrays within it. I've read that there's no ByRef for arrays (at least, not completely - I think you can get an array ByRef but not set one?). I am not sure if that would create problems with trying to set it.
But ultimately, I just want to end up with my JSON data represented easily in an OO way, so that in my excel ultimately I can just do
myFancyCat.Name or myFancyCat.Socks.Count or myFancyCat.Socks(1).Colour etc
It seems much harder than it looks to simply deserialise JSON into 'objects' in vba.
Please, try the next way:
Insert a class module, name it FancyCat and copy the next code:
Option Explicit
Private arrL As Object
Public myName As String, myAge As Long
Public Sub Class_Initialize()
Set arrL = CreateObject("System.Collections.ArrayList")
End Sub
Public Property Let Name(strName As String)
myName = strName
End Property
Public Property Let Age(lngAge As String)
myAge = lngAge
End Property
Public Property Let SocksAdd(sMember)
arrL.Add sMember
End Property
Public Property Get Socks() As Variant
Socks = arrL.toarray()
End Property
Use it in the next testing Sub:
Sub testClassDictListArray()
Dim myFancyCat As New FancyCat, myData As Object
Dim arrSocks, sock
Set myData = CreateObject("Scripting.Dictionary") 'this should be the dictionary returned by ParseJSON
myData.Add "Name", "John Doe": myData.Add "Age", 35
myData.Add "Socks", Array("Blue", "White", "Red", "Green", "Yellow")
myFancyCat.Name = myData("Name")
myFancyCat.Age = myData("Age")
For Each sock In myData("Socks")
myFancyCat.SocksAdd = sock
Next sock
arrSocks = myFancyCat.Socks
Debug.Print Join(arrSocks, "|")
End Sub
I am not sure I perfectly understand the scenario you try putting in discussion...
If you want to benefit of instellisense suggestions, I will tell you what references to be added. Even, I will send two pieces of code to automatically add the necessary references (I mean, Scripting.Dictionary and ArrayList`).
Please, test it and send some feedback.
In your class:
Private m_Name As String
Private m_Socks() As String
Public Property Let Name(Name As String)
m_Name = Name
End Property
Public Property Get Name() As String
Name = m_Name
End Property
Public Sub SetSize(Quantity As Long)
ReDim m_Socks(1 To Quantity)
End Sub
Public Property Let Socks(Index As Long, Sock As String)
m_Socks(Index) = Sock
End Property
Public Property Get Socks(Index As Long) As String
Socks = m_Socks(Index)
End Property
In a regular module:
Sub UseFancyCat()
Dim MyFancyCat As FancyCat
Set MyFancyCat = New FancyCat
MyFancyCat.Name = "Fancy Name"
MyFancyCat.SetSize 2
MyFancyCat.Socks(1) = "Sock1"
MyFancyCat.Socks(2) = "Sock2"
Debug.Print MyFancyCat.Name
Debug.Print MyFancyCat.Socks(1)
Debug.Print MyFancyCat.Socks(2)
End Sub
Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
Type DBResult (Tag As Object, **Columns As Map**, Rows As List)
Type DBCommand (Name As String, Parameters() As Object)
Private const rdcLink As String = "http://192.168.8.100:17178/rdc"
End Sub
This is the methods for process globals . Here columns As Map is initialized .
However the line bolded in the below code gives an error as , "Object converted to String. This is probably a programming mistake. (warning #7)"
Sub GetRecord
Dim req As DBRequestManager = CreateRequest
Dim cmd As DBCommand = CreateCommand("selectAllNames", Null)
Wait For (req.ExecuteQuery(cmd, 0, Null)) JobDone(j As HttpJob)
If j.Success Then
req.HandleJobAsync(j, "req")
Wait For (req) req_Result(res As DBResult)
'work with result
'req.PrintTable(res)
***Log(res.Columns)***
ListViewListTable.Clear
For Each row() As Object In res.Rows
Dim oBitMap As Bitmap
Dim buffer() As Byte
buffer = row(res.Columns.Get("gambar"))
oBitMap = req.BytesToImage(buffer)
ListViewListTable.AddTwoLinesAndBitmap(row(1), "See more...", oBitMap)
Next
Else
Log("ERROR: " & j.ErrorMessage)
End If
j.Release
End Sub
So what should I do to remove the error?
If columns is a map? (which it looks to be?).
Then to display the columns you can use this:
For Each MyKey As String in res.Columns.Keys
log("Key name = " & MyKey)
log("Key value = " & res.Columns.Get(MyKey))
Next
I want to check a file for a particular word the way I have found posted on various forums is to use the following code...
Dim content = My.Computer.FileSystem.ReadAllText(filePath)
If content.Contains("stringToSearch") Then
'Do your stuff
End If
Which is okay until you discover that it will search and match compound words and the likes. For instance If I search for the string light in a file and it's not there but instead the word lightning is, it will still register as having found a match... Is there a way to find and exact word using VB.net?
As mentioned by Andrew Morton, Regex makes this kind of thing very easy. For instance, if you made a function like this:
Public Function ContainsWord(input As String, word As String) As Boolean
Return Regex.IsMatch(input, $"\b{word}\b")
End Function
You could use it like this:
Dim content = My.Computer.FileSystem.ReadAllText(filePath)
If ContainsWord(content, "stringToSearch") Then
'Do your stuff
End If
If you wanted to, you could even make it an extension method on the String type, by putting it in a Module and adding the ExtensionAttribute, like this:
<Extension>
Private Function ContainsWord(input As String, word As String) As Boolean
Return Regex.IsMatch(input, $"\b{word}\b")
End Function
And then you could call it like this:
Dim content = My.Computer.FileSystem.ReadAllText(filePath)
If content.ContainsWord("stringToSearch") Then
'Do your stuff
End If
Another method, using Regex.Matches, which allows to search for a collection of words and returns a Dictionary(Of String, Integer()).
The Dictionary Key represent the matched word, the Value, as an Array of Integers, all the positions inside the File where the word was found.
The extension method requires 2 parameters:
- the path of the file to search
- a boolean value, used to specify whether the search should be case sensitive.
Proposed as an extension method of IEnumerable(Of String):
Dim fileName As String = "[File Path]"
Dim searchWords As String() = {"light", "lighting", "clip", "clipper", "somethingelse"}
Dim result = searchWords.FindWords(fileName, False)
Print a result of the matches found:
result.ToList().ForEach(
Sub(w)
Console.WriteLine($"Word: {w.Key} Positions: {String.Join(", ", w.Value)}")
End Sub)
Extension method:
Imports System.IO
Imports System.Runtime.CompilerServices
Imports System.Text
Imports System.Text.RegularExpressions
Module modIEnumerableExtensions
<Extension()>
Public Function FindWords(words As IEnumerable(Of String),
fileName As String,
caseSentive As Boolean) As Dictionary(Of String, Integer())
Dim pattern As StringBuilder = New StringBuilder()
pattern.Append(String.Concat(words.Select(Function(w) $"\b{w}\b|")))
Dim options As RegexOptions = RegexOptions.Compiled Or
If(caseSentive, RegexOptions.Multiline, RegexOptions.IgnoreCase Or RegexOptions.Multiline)
Dim regx As New Regex(pattern.ToString().TrimEnd("|"c), options)
Dim matches As MatchCollection = regx.Matches(File.ReadAllText(fileName))
Dim groups = matches.OfType(Of Match).
GroupBy(Function(g) g.Value).
ToDictionary(Function(g) g.Key, Function(g) g.Select(Function(m) m.Index).ToArray())
Return groups
End Function
End Module
The shortest and fastest way to do this is using ReadLines with LINQ queries, specialy when you are working with a large files.
Dim myword As String = "Book"
Dim reg = New Regex("\b" & myword & "\b", RegexOptions.IgnoreCase)
Dim res = From line In File.ReadLines(largeFileName)
Where reg.IsMatch(line)
If your file containts "Book", "Books", "Book." and "Book," the results will be:
Book
Book,
Book.
And you can working with results as following
TextBox1.Text = resLines.Count
Or
TextBox1.Text = resLines(0)
Edited to make it consering "." and "," etc.
I'm trying to create a macro that can collect data from an excel spreadsheet in the local active workbook and then create header file which I would later incorporate into my project. But for the life of me I must be missing something so DUMB that I can't create a working function that returns a string (which would construct a C++ structure) to the calling function. I've simplified the example code to is absolute bare minimum to isolate the problem but I still cannot figure out what I'm doing wrong. I'm not an expert at VBA but I know how to create code and I can't narrow down what VBA is unhappy about. I keep getting "compile error, syntax error." Please copy the following code into your module and see if compiles properly for you. If you know where I went wrong please let me know. Much Appreciated!!!
Sub CREATE_FACTORY_SETTING_HEADER()
Dim FS, TSsource
Set FS = CreateObject("Scripting.FileSystemObject")
Dim TSout
Set TSout = FS.Createtextfile("HeaderFile.h", True)
Dim fileHeading As String
fileHeading = "File Heading for Header file"
Dim fileBody As String
fileBody = "Some initial file body lines"
fileBody = fileBody & createStructBody
TSout.Write fileHeading & fileBody
TSout.Close
End Sub
Public Function createStructBody() As String
Dim structBody As String
structBody = "Hey I'm a struct body, but I can't be returned for some reason"
Return structBody
End Function
Both VBA and VBScript use 'assignment to function' (instead of 'return' or 'result of last statement') to return results from functions. So
Public Function createStructBody() As String
createStructBody = "Hey I'm a string and can be returned."
End Function
I am writing a small utility in VB6 which is calling the C#.Net class (which brings list of printers) but while calling the C# method, it is throwing below error and I am not able to compile/run the application. Can someone please help on this?
VB6 Code:
Dim retval As Integer
Dim tbp As NamespaceXYZ.CGETList
Dim a As String
Dim col As New Collection
Set tbp = New CGETList
retval = tbp.GetDefaultPrinterAndList(col, a)
C# definition for the method.
public void GetDefaultPrinterAndList(ref Microsoft.VisualBasic.Collection vntPrinterList, ref string defaultPrinter)
{
error:
You declared tbp, but forgot to initialise it.
Dim tbp As NamespaceXYZ.CGETList
'tbp value is currently Nothing
Set tbp = New NamespaceXYZ.CGETList
'now it's something.
Note that the above assumes that the NamespaceXYZ.CGETList class has a default constructor i.e. you can create a new object just using New. Some classes don't have this; they require you to create objects in other ways.