I'm trying to find an alternative way to solve the problem I'm stuck on here. I'm using MSTest to select one of a set of arrays of bytes to pass to a function under test.
I'm trying this approach as I haven't been able to get MSTest working directly passing an array of bytes to the test function.
I want to set up a Private ReadOnly jagged array of arrays of Bytes (TestMsgs) as part of my test class to allow the test subroutine to access elements one by one. Currently I'm getting error BC30201 "Expression Expected" as below. Something is missing in my initialisation, but I can't find any example on how to initialise this jagged array.
Public Class DecoderTests
Private ReadOnly TestMsgs As Byte()() = New Byte(2)() {
New Byte() {&HA1, &HB2, &HC3}, 'Test array should Pass
New Byte() {&HA2, &HB3}, 'Test array should Fail
} <========= Error BC30201 Here
Private DecoderInstance
Here is the full code of my test (Simplified to debug the original issue)
Test Class
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Namespace TestDecoder.Tests
<TestClass>
Public Class DecoderTests
Private ReadOnly TestMsgs As Byte()() = New Byte(2)() {
New Byte() {&HA1, &HB2, &HC3}, 'Test array should Pass
New Byte() {&HA2, &HB3}, 'Test array should Fail
}
Private DecoderInstance
<DataTestMethod>
<DataRow(0)>
<DataRow(1)>
Public Sub ParseTestData(message_number)
Dim result As Boolean
DecoderInstance = New Decoder()
result = DecoderInstance.parse(TestMsgs(message_number)(0))
Assert.IsTrue(result, "Failed the dummy test")
End Sub
End Class
End Namespace
Simplified Class under test:
Imports Microsoft.VisualBasic
Public Class Decoder
Function parse(rxchar As Byte) As Boolean
Return rxchar = &H41
End Function
End Class
I just tried this code and there were no errors:
Private ReadOnly TestMsgs As Byte()() = {
New Byte() {&HA1, &HB2, &HC3},
New Byte() {&HA2, &HB3}
}
I think the issue with your original code was that you were specifying the size of the array explicitly and also initialising it, therefore specifying the size of the array implicitly. I just tested this code and it worked too:
Private ReadOnly TestMsgs As Byte()() = New Byte()() {
New Byte() {&HA1, &HB2, &HC3},
New Byte() {&HA2, &HB3}
}
Related
Need help to deserialize complex json file using Visual Basic and Newtonsoft.Json library.
Here is the sample of Json file:
{
"value": [
{
"Id": 12345,
"Name": "Test",
"CardCount": 0,
"DailySpendLimit": 0.00,
"WeeklySpendLimit": 50.00,
"MonthlySpendLimit": 100.00,
"YearlySpendLimit": 1000.00,
"LifetimeSpendLimit": 0.00,
"MerchantCategories": [
"#{Id=11111; Name=Associations \u0026 Organizations; Description=Post Office, local and federal government services, religious organizations.; TransactionLimit=0.00; DailySpendLimit=0.00; WeeklySpendLimit=0.00; MonthlySpendLimit=0.00; YearlySpendLimit=0.00; LifetimeSpendLimit=0.00}",
"#{Id=22222; Name=Automotive Dealers; Description=Vehicle dealerships (car, RV, motorcycle, boat, recreational).; TransactionLimit=0.00; DailySpendLimit=0.00; WeeklySpendLimit=0.00; MonthlySpendLimit=0.00; YearlySpendLimit=0.00; LifetimeSpendLimit=0.00}"
]
}
],
"Count": 1
}
I had setup classes:
Public Class TEST_AdvRules
<JsonProperty("value")>
Public Property ValueDetails As ValueList()
Public Class ValueList
<JsonProperty("Id")>
Public Property RuleId As Integer
<JsonProperty("Name")>
Public Property RuleName As String = ""
Public Property CardCount As Integer
Public Property DailySpendLimit As Double
Public Property WeeklySpendLimit As Double
Public Property MonthlySpendLimit As Double
Public Property YearlySpendLimit As Double
Public Property LifetimeSpendLimit As Double
<JsonProperty("MerchantCategories")>
Public Property MerchantCategories() As List(Of MerchantCategoriesList)
End Class
Public Class MerchantCategoriesList
Public Property Id As Integer
Public Property Name As String = ""
Public Property Description As String = ""
Public Property TransactionLimit As Double
Public Property DailySpendLimit As Double
Public Property WeeklySpendLimit As Double
Public Property MonthlySpendLimit As Double
Public Property YearlySpendLimit As Double
Public Property LifetimeSpendLimit As Double
End Class
End Class
The code for deserilization:
Dim jsonStringP = IO.File.ReadAllText(PathJson & SecFileName)
Dim serSettings = New JsonSerializerSettings()
serSettings.ContractResolver = New CamelCasePropertyNamesContractResolver()
Dim RulesPEX = JsonConvert.DeserializeObject(Of TEST_AdvRules)(jsonStringP, serSettings)
But it errors out on the deserialization line - can't convert MerchantCategories.
I have never dealt with Json with [ "# array sequence. Am I processing it wrong?
Also - there is a character sequence '\u0026 ' within Merchant categories. Does it require special handling? Thank you!
The problem is that MerchantCategories is a string collection. You will have to convert each string to object using a custom string analyzer. Last time I worked with Vb.net was more then 10 years ago, so I can give you a solution in C# , your part would be to convert it to VB.Net. I don't feel like installing VB.NET compilliar at my mashine.
using Newtonsoft.Json;
var jsonObject = JObject.Parse(jsonStringP);
var jsonArr=new JArray();
foreach (var item in (JArray) jsonObject["value"][0]["MerchantCategories"])
{
jsonArr.Add(ConvertToJObject(item.ToString()));
}
jsonObject["value"][0]["MerchantCategories"]=jsonArr;
TEST_AdvRules RulesPEX = jsonObject.ToObject<TEST_AdvRules>();
this function converts a string to a JObject
public JObject ConvertToJObject(string str)
{
var strStart=str.IndexOf("{")+1;
var strEnd=str.IndexOf("}")-2;
var arr = str.Substring(strStart, strEnd).Split(";").Select(c => c.Split("="));
var jObj = new JObject();
foreach (var item in arr)
jObj.Add(new JProperty(item[0].Trim(), item[1].Trim()));
return jObj;
}
let Visual Studio create the class for you:
copy the whole json file content to the clipboard
in VS, add a new empty class -> Edit -> Paste Special -> paste json as class
I tend to add a shared Read-Method for easier construction.
Of course you can rename your classes as you wish :-)
I'm on german VS but you get the idea :-)
Translating Serge's answer into VB (note untested code so it's possible there are some minor errors)...
Imports Newtonsoft.Json
'...
Dim jsonObject = JObject.Parse(jsonStringP)
Dim jsonArr = new JArray()
For Each item In CType(jsonObject("value")(0)("MerchantCategories"), JArray)
jsonArr.Add(ConvertToJObject(item.ToString()))
Next
jsonObject("value")(0)("MerchantCategories") = jsonArr
Dim RulesPEX As TEST_AdvRules = jsonObject.ToObject(Of TEST_AdvRules)()
and
Public Function ConvertToJOBject(ByVal str As String) As JObject
Dim strStart = str.IndexOf("{") + 1
Dim strEnd = str.IndexOf("}") - 2
Dim arr = str.Substring(strStart, strEnd).Split(";").Select(Function(c) c.Split("="))
Dim jObj = New JObject()
For Each item In arr
jObj.Add(New JProperty(item(0).Trim(), item(1).Trim()))
Next
Return jObj
End Function
I have an array called lblTables which contains 21 lables of my form. This works perfectly fine. The code for it is below:
Public lblTables() As Label
Public Sub New()
InitializeComponent()
lblTables = New Label() {Label2, table1, table2, table3, table4, table5, table6, table7, table8, table9, table10,
table11, table12, table13, table14, table15, table16, table17, table18, table19, table20}
End Sub
I need to create another array called lblStartTimes which will contain another 21 labels. So i proceeded as follows:
Private lblStartTimes() As Label
Public Sub New()
InitializeComponent()
lblStartTimes = New Label() {startTime1, startTime2, startTime3, startTime4, startTime5, startTime6,
startTime7, startTime8, startTime9, startTime10, startTime11, startTime12, startTime13, startTime14,
startTime15, startTime16, startTime17, startTime18, startTime19, startTime20}
End Sub
But when i tried to do that i got an error saying Public Sub New() has multiple definitions with identical signatures. So after doing a research i found the easiest way of doing this would be as follows:
Public lblStartTimes() As Label = {Label3, startTime1, startTime2, startTime3, startTime4, startTime5, startTime6,
startTime7, startTime8, startTime9, startTime10, startTime11, startTime12, startTime13, startTime14,
startTime15, startTime16, startTime17, startTime18, startTime19, startTime20}
but when i use this method i'm constantly receiving a System.NullReferenceException error. i need to declare lblStartTimes the same OR similar way i declared lblTables array without getting the multiple definitions error. How can i achieve this?
Like already suggested in the comments, you are allowed to write multiple lines in the constructor. Just move the second array initialization code to the first constructor:
Public lblTables() As Label
Public lblStartTimes() As Label
Public Sub New()
InitializeComponent()
lblTables = New Label() {Label2, table1, table2, table3, table4, table5, table6, table7, table8, table9, table10,
table11, table12, table13, table14, table15, table16, table17, table18, table19, table20}
lblStartTimes = New Label() {startTime1, startTime2, startTime3, startTime4, startTime5, startTime6,
startTime7, startTime8, startTime9, startTime10, startTime11, startTime12, startTime13, startTime14,
startTime15, startTime16, startTime17, startTime18, startTime19, startTime20}
End Sub
Consider a text file stored in an online location that looks like this:
;aiu;
[MyEditor45]
Name = MyEditor 4.5
URL = http://www.myeditor.com/download/myeditor.msi
Size = 3023788
Description = This is the latest version of MyEditor
Feature = Support for other file types
Feature1 = Support for different encodings
BugFix = Fix bug with file open
BugFix1 = Fix crash when opening large files
BugFix2 = Fix bug with search in file feature
FilePath = %ProgramFiles%\MyEditor\MyEditor.exe
Version = 4.5
Which details information about a possible update to an application which a user could download. I want to load this into a stream reader, parse it and then build up a list of Features, BugFixes etc to display to the end user in a wpf list box.
I have the following piece of code that essentially gets my text file (first extracting its location from a local ini file and loads it into a streamReader. This at least works although I know that there is no error checking at present, I just want to establish the most efficient way to parse this first. One of these files is unlikely to ever exceed more than about 250 - 400 lines of text.
Dim UpdateUrl As String = GetUrl()
Dim client As New WebClient()
Using myStreamReader As New StreamReader(client.OpenRead($"{UpdateUrl}"))
While Not myStreamReader.EndOfStream
Dim line As String = myStreamReader.ReadLine
If line.Contains("=") Then
Dim p As String() = line.Split(New Char() {"="c})
If p(0).Contains("BugFix") Then
MessageBox.Show($" {p(1)}")
End If
End If
End While
End Using
Specifically I'm looking To collate the information about Features, BugFixes and Enhancements. Whilst I could construct what would in effect be a rather messy if statement I feel sure that there must be a more efficient way to do this , possibly involving linq. I'd welcome any suggestions.
I have added the wpf tag on the off chance that someone reading this with more experience of displaying information in wpf listboxes than I have might just spot a way to effectively define the info I'm after in such a way that it could then be easily displayed in a wpf list box in three sections (Features, Enhancements and BugFixes).
Dom, Here is an answer in C#. I will try to convert it to VB.Net momentarily. First, since the file is small, read all of it into a list of strings. Then select the strings that contain an "=" and parse them into data items that can be used. This code will return a set of data items that you can then display as you like. If you have LinqPad, you can test thecode below, or I have the code here: dotnetfiddle
Here is the VB.Net version: VB.Net dotnetfiddle
Imports System
Imports System.Collections.Generic
Imports System.Linq
Public Class Program
Public Sub Main()
Dim fileContent As List(Of String) = GetFileContent()
Dim dataItems = fileContent.Where(Function(c) c.Contains("=")).[Select](Function(c) GetDataItem(c))
dataItems.Dump()
End Sub
Public Function GetFileContent() As List(Of String)
Dim contentList As New List(Of String)()
contentList.Add("sb.app; aiu;")
contentList.Add("")
contentList.Add("[MyEditor45]")
contentList.Add("Name = MyEditor 4.5")
contentList.Add("URL = http://www.myeditor.com/download/myeditor.msi")
contentList.Add("Size = 3023788")
contentList.Add("Description = This is the latest version of MyEditor")
contentList.Add("Feature = Support for other file types")
contentList.Add("Feature1 = Support for different encodings")
contentList.Add("BugFix = Fix bug with file open")
contentList.Add("BugFix1 = Fix crash when opening large files")
contentList.Add("BugFix2 = Fix bug with search in file feature")
contentList.Add("FilePath = % ProgramFiles %\MyEditor\MyEditor.exe")
contentList.Add("Version = 4.5")
Return contentList
End Function
Public Function GetDataItem(value As String) As DataItem
Dim parts = value.Split("=", 2, StringSplitOptions.None)
Dim dataItem = New DataItem()
dataItem.DataType = parts(0).Trim()
dataItem.Data = parts(1).Trim()
Return dataItem
End Function
End Class
Public Class DataItem
Public DataType As String
Public Data As String
End Class
Or, in C#:
void Main()
{
List<string> fileContent = GetFileContent();
var dataItems = fileContent.Where(c => c.Contains("="))
.Select(c => GetDataItem(c));
dataItems.Dump();
}
public List<string> GetFileContent()
{
List<string> contentList = new List<string>();
contentList.Add("sb.app; aiu;");
contentList.Add("");
contentList.Add("[MyEditor45]");
contentList.Add("Name = MyEditor 4.5");
contentList.Add("URL = http://www.myeditor.com/download/myeditor.msi");
contentList.Add("Size = 3023788");
contentList.Add("Description = This is the latest version of MyEditor");
contentList.Add("Feature = Support for other file types");
contentList.Add("Feature1 = Support for different encodings");
contentList.Add("BugFix = Fix bug with file open");
contentList.Add("BugFix1 = Fix crash when opening large files");
contentList.Add("BugFix2 = Fix bug with search in file feature");
contentList.Add("FilePath = % ProgramFiles %\\MyEditor\\MyEditor.exe");
contentList.Add("Version = 4.5");
return contentList;
}
public DataItem GetDataItem(string value)
{
var parts = value.Split('=');
var dataItem = new DataItem()
{
DataType = parts[0],
Data = parts[1]
};
return dataItem;
}
public class DataItem
{
public string DataType;
public string Data;
}
The given answer only focuses on the first part, converting the data to a structure that can be shaped for display. But I think you main question is how to do the actual shaping.
I used a somewhat different way to collect the file data, using Microsoft.VisualBasic.FileIO.TextFieldParser because I think that makes coding just al little bit easier:
Iterator Function GetTwoItemLines(fileName As String, delimiter As String) _
As IEnumerable(Of Tuple(Of String, String))
Using tfp = New TextFieldParser(fileName)
tfp.TextFieldType = FieldType.Delimited
tfp.Delimiters = {delimiter}
tfp.HasFieldsEnclosedInQuotes = False
tfp.TrimWhiteSpace = False
While Not tfp.EndOfData
Dim arr = tfp.ReadFields()
If arr.Length >= 2 Then
Yield Tuple.Create(arr(0).Trim(), String.Join(delimiter, arr.Skip(1)).Trim())
End If
End While
End Using
End Function
Effectively the same thing happens as in your code, but taking into account Andrew's keen caution about data loss: a line is split by = characters, but the second field of a line consists of all parts after the first part with the delimiter re-inserted: String.Join(delimiter, arr.Skip(1)).Trim().
You can use this function as follows:
Dim fileContent = GetTwoItemLines(file, "=")
For display, I think the best approach (most efficient in terms of lines of code) is to group the lines by their first items, removing the numeric part at the end:
Dim grouping = fileContent.GroupBy(Function(c) c.Item1.TrimEnd("0123456789".ToCharArray())) _
.Where(Function(k) k.Key = "Feature" OrElse k.Key = "BugFix" OrElse k.Key = "Enhancement")
Here's a Linqpad dump (in which I took the liberty to change one item a bit to demonstrate the correct dealing with multiple = characters:
You could do it with Regular Expressions:
Imports System.Text.RegularExpressions
Private Function InfoReader(ByVal sourceText As String) As List(Of Dictionary(Of String, String()))
'1) make array of fragments for each product info
Dim products = Regex.Split(sourceText, "(?=\[\s*\w+\s*])")
'2) declare variables needed ahead
Dim productProperties As Dictionary(Of String, String)
Dim propertyNames As String()
Dim productGroupedProperties As Dictionary(Of String, String())
Dim result As New List(Of Dictionary(Of String, String()))
'2) iterate along fragments
For Each product In products
'3) work only in significant fragments ([Product]...)
If Regex.IsMatch(product, "\A\[\s*\w+\s*]") Then
'4) make array of property lines and extract dictionary of property/description
productProperties = Regex.Split(product, "(?=^\w+\s*=)", RegexOptions.Multiline).Where(
Function(s) s.Contains("="c)
).ToDictionary(
Function(s) Regex.Match(s, "^\w+(?=\s*=)").Value,
Function(s) Regex.Match(s, "(?<==\s+).*(?=\s+)").Value)
'5) extract distinct property names, ignoring numbered repetitions
propertyNames = productProperties.Keys.Select(Function(s) s.TrimEnd("0123456789".ToCharArray)).Distinct.ToArray
'6) make dictionary of distinctProperty/Array(Of String){description, description1, ...}
productGroupedProperties = propertyNames.ToDictionary(
Function(s) s,
Function(s) productProperties.Where(
Function(kvp) kvp.Key.StartsWith(s)
).Select(
Function(kvp) kvp.Value).ToArray)
'7) enlist dictionary to result
result.Add(productGroupedProperties)
End If
Next
Return result
End Function
Basic arrays question:
I have declared a public array and initialized it with values. In another private sub, I want to fill that public array with values. What is the easiest way to do it?
The double variables in the array are also declared public. The following code is the array created and initialized in a public module. In the private sub, I am changing the values of the double variables (ch0LowLmt, ch1LowLmt, etc, etc) and now I just simply want to reinitialize the array with the new values. What is the simplest way to do it?
Public AlarmLowLimit() As Double = New Double(15) {Ch0LowLmt, Ch1LowLmt, Ch2LowLmt, Ch3LowLmt, Ch4LowLmt, Ch5LowLmt, Ch6LowLmt, Ch7LowLmt, Ch8LowLmt, Ch9LowLmt, _
Ch10LowLmt, Ch11LowLmt, Ch12LowLmt, Ch13LowLmt, Ch14LowLmt, Ch15LowLmt}
Because Double is a value type, you do need to change it manually in the way you are working.
Another way, is to encapsulate the double var inside a class, which can be put inside your array, so it will be a reference type.
In this case you'd just update your new reference type objects in the private function you have mentioned. They will also change in your array.
See a nice solution here.
And the code example in Vb.Net:
Public Class MyObjectWithADouble
Public Property Element() As Double
Get
Return m_Element
End Get
Set
m_Element = Value
End Set
End Property
Private m_Element As Double
' property is optional, but preferred.
End Class
Dim obj = New MyObjectWithADouble()
obj.Element = 5.0
I understand that List, Array, Object etc. types "copied" by reference. However my natural and ordinary intend is to just have a "copy" of it in this context where I intentionally use ReadOnly instead of Read/Write property. In below sample the ReadOnly 'Extensions' property get change through 'm_extensions' reference change. Regardless, I think this behavior is incorrect and I have to do extra work to prevent ReadOnly properties from being overwritten. Is there any built in keyword to use for 'm_extensions' value protection?
Public Classs A
' more properties and methods here...
Private m_extensions() As String = {"*.abc", "*.def"}
Public ReadOnly Property Extensions() As String()
Get
Return m_extensions
End Get
End Property
End Class
Public Classs B
' more stuff here...
Private Function BuildFilter() As String
Dim l() As String = A.Extensions
Dim s As String = String.Empty
For m As Integer = 0 To l.Length - 1
Select Case l(m).ToLower
Case "*.*" : s = "All Files"
Case "*.abc" : s = "ABC File"
Case "*.def" : s = "DEF File"
Case Else : s = "XYZ File " + m.ToString
End Select
l(m) = String.Format("{1} ({0})|{0}", l(m), s)
Next
Return String.Join("|", l)
End Function
End Class
Readonly modifier means that anything using the property cannot change the reference that you protected this way (e.g. cannot set it to Nothing). It doesn't prevent changing the values in the array returned from that property.
One way around it could be to copy the array inside the property. This will prevent modifications of the original array:
Public ReadOnly Property Extensions() As String()
Get
Return m_extensions.Clone()
End Get
End Property