Given Json input like the following:
{
"Group A": {
"Prop A": 42,
"Prop B": true,
"Prop C": [ "Hello", "World!" ]
},
"Group B": {
"Prop A": 72
}
}
I would like to populate the following data structure:
Dictionary<string, Dictionary<string, string>> Groups;
Such that the following statement would be true (differences in whitespace are not important):
Groups["Group A"]["Prop C"] == "[\"Hello\",\"World!\"]"
Inefficient Solution:
The following seems to solve this problem, but it is quite inefficient since it incurs unnecessary intermediary serialization:
Groups = new Dictionary<string, Dictionary<string, string>>();
foreach (var jsonGroup in JObject.Parse(jsonText)) {
var group = new Dictionary<string, string>();
Groups[jsonGroup.Key] = group;
foreach (var jsonProperty in jsonGroup.Value.Children<JProperty>())
group[jsonProperty.Name] = JsonConvert.SerializeObject(jsonProperty.Value);
}
Sadly JProperty.Value.ToString() seems to return odd values, for instance "False" instead of "false".
It seems that the intended behaviour of JValue.ToString() is to return the enclosed value as a string rather than encode to JSON. Instead JValue.ToString(IFormatProvider) should be used to produce a valid JSON encoded string.
https://github.com/JamesNK/Newtonsoft.Json/issues/266
However, in my particular case it seems to make better sense to retain the Json.NET representation since this avoids reparsing the same JSON content multiple times:
Groups = new Dictionary<string, Dictionary<string, JToken>>();
foreach (var jsonGroup in JObject.Parse(jsonText)) {
var group = new Dictionary<string, JToken>();
Groups[jsonGroup.Key] = group;
foreach (var jsonProperty in jsonGroup.Value.Children<JProperty>())
group[jsonProperty.Name] = jsonProperty.Value;
}
And then, instead of parsing these sub-structures with JObject.Parse I can use JToken.ToObject<T> to convert them into the desired representation:
bool myValue = Groups["Group A"]["Prop B"].ToObject<bool>();
Related
I am parsing a template file which will contain certain keys that I need to map values to. Take a line from the file for example:
Field InspectionStationID 3 {"PVA TePla #WSM#", "sw#data.tool_context.TOOL_SOFTWARE_VERSION#", "#data.context.TOOL_ENTITY#"}
I need to replace the string within the # symbols with values from a dictionary.
So there can be multiple keys from the dictionary. However, not all strings inside the # are in the dictionary so for those, I will have to replace them with empty string.
I cant seem to find a way to do this. And yes I have looked at this solution:
check if string contains dictionary Key -> remove key and add value
For now what I have is this (where I read from the template file line by line and then write to a different file):
string line = string.Empty;
var dict = new Dictionary<string, string>() {
{ "data.tool_context.TOOL_SOFTWARE_VERSION", "sw0.2.002" },
{"data.context.TOOL_ENTITY", "WSM102" }
};
StringBuilder inputText = new StringBuilder();
StreamWriter writeKlarf = new StreamWriter(klarfOutputNameActual);
using (StreamReader sr = new StreamReader(WSMTemplatePath))
{
while((line = sr.ReadLine()) != null)
{
//Console.WriteLine(line);
if (line.Contains("#"))
{
}
else
{
writeKlarf.WriteLine(line)
}
}
}
writeKlarf.Close();
THe idea is that for each line, replace the string within the # and the # with match values from the dictionary if the #string# is inside the dictionary. How can I do this?
Sample Output Given the line above:
Field InspectionStationID 3 {"PVA TePla", "sw0.2.002", "WSM102"}
Here because #WSM# is not the dictionary, it is replaced with empty string
One more thing, this logic only applies to the first qurter of the file. The rest of the file will have other data that will need to be entered via another logic so I am not sure if it makes sense to read the whole file in into memory just for the header section?
Here's a quick example that I wrote for you, hopefully this is what you're asking for.
This will let you have a <string, string> Dictionary, check for the Key inside of a delimiter, and if the text inside of the delimiter matches the Dictionary key, it will replace the text. It won't edit any of the inputted strings that don't have any matches.
If you want to delete the unmatched value instead of leaving it alone, replace the kvp.Value in the line.Replace() with String.Empty
var dict = new Dictionary<string, string>() {
{ "test", "cool test" }
};
string line = "#test# is now replaced.";
foreach (var kvp in dict)
{
string split = line.Split('#')[1];
if (split == kvp.Key)
{
line = line.Replace($"#{split}#", kvp.Value);
}
Console.WriteLine(line);
}
Console.ReadLine();
If you had a list of tuple that were the find and replace, you can read the file, replace each, and then rewrite the file
var frs = new List<(string F, string R)>(){
("#data.tool_context.TOOL_SOFTWARE_VERSION#", "sw0.2.002"),
("#otherfield#", "replacement here")
};
var i = File.ReadAllText("path");
frs.ForEach(fr => i = i.Replace(fr.F,fr.R));
File.WriteAllText("path2", i);
The choice to use a list vs dictionary is fairly arbitrary; List has a ForEach method but it could just as easily be a foreach loop on a dictionary. I included the ## in the find string because I got the impression the output is not supposed to contain ##..
This version leaves alone any template parameters that aren't available
You can try matching #...# keys with a help of regular expressions:
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
...
static string MyReplace(string value, IDictionary<string, string> subs) => Regex
.Replace(value, "#[^#]*#", match => subs.TryGetValue(
match.Value.Substring(1, match.Value.Length - 2), out var item) ? item : "");
then you can apply it to the file: we read file's lines, process them with a help of Linq and write them into another file.
var dict = new Dictionary<string, string>() {
{"data.tool_context.TOOL_SOFTWARE_VERSION", "sw0.2.002" },
{"data.context.TOOL_ENTITY", "WSM102" },
};
File.WriteAllLines(klarfOutputNameActual, File
.ReadLines(WSMTemplatePath)
.Select(line => MyReplace(line, dict)));
Edit: If you want to switch off MyReplace from some line on
bool doReplace = true;
File.WriteAllLines(klarfOutputNameActual, File
.ReadLines(WSMTemplatePath)
.Select(line => {
//TODO: having line check if we want to keep replacing
if (!doReplace || SomeCondition(line)) {
doReplace = false;
return line;
}
return MyReplace(line, dict)
}));
Here SomeCondition(line) returns true whenever header ends and we should not replace #..# any more.
What is the best way to split the name/description of the value into a dictionary <string, string>?
string test = postedByUser='Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'
so ideally i want
dictionary key = postedByUser, value = Jason, Bourne
dictionary key = postedById, value = 48775
etc
code added so far
string test = #"postedByUser=Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'";
Dictionary<string, string> dict = new Dictionary<string, string>();
List<string> lst = test.Split('=').ToList();
foreach(string item in lst)
{
// cant figure out how edit the orginal string to remove the item that has
//been split by the '='
}
Well you can the following code will solve your problem but I suggest you to add more exception handling to make the code robust.
string test = #"postedByUser='Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'";
Dictionary<string, string> dict = new Dictionary<string, string>();
List<string> keyvalues = test.Split("' ").ToList();
foreach(var keyvalue in keyvalues)
{
var splitKeyValue = keyvalue.Split('=');
dict.Add(splitKeyValue[0], splitKeyValue[1]);
}
EDIT:
For .NET Framework 4.6,
List<string> keyvalues = test.Split(new string[] { "' " }, StringSplitOptions.None).ToList();
Try this one (quite ugly but should work):
var dict = test.Split("' ").Select(t=>string.Concat(t,"'").Split("=")).ToDictionary(t=>t[0],t=>t[1]);
Use String.Split(..) to separate out your string for processing.
String.Split documentation
First split the string based on a ' ' space character as a separator e.g.
var splitStrings = test.Split(' ', StringSplitOptions.None);
then run through each string in strings and separate using '='. This will give you a list of 2 strings where the first is your key and the second is your value.
Regular expressions can be used to solve this problem:
string text = #"postedByUser='Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'";
Dictionary<string, string> result = new Dictionary<string, string>();
foreach (Match m in Regex.Matches(text, #"(\w+)=\'(.+?)\'"))
{
result.Add(m.Groups[1].Value, m.Groups[2].Value);
}
Here is complete sample.
This question already has answers here:
C# String replace with dictionary
(8 answers)
Closed 3 years ago.
I have a lstSubs List<KeyValuePair<string, string>
which contain value
FNAME, "ABC"
LNAME ,"XYZ"
VAR001, "VAR002"
VAR002 , "ActualValueforVAR001"
VAR003, "VAR004"
VAR004 , "VAR005"
VAR005, "ActualValueforVAR003"
I have a String like envelop "Hello [FNAME] [LNAME] you have created a request for [VAR001] which got assigned to [VAR003]"
var regex = new Regex(#"\[(.*?)\]");
var matches = regex.Matches(envelop.ToString());
foreach (Match match in matches)
{
columnValue = linq to get the value from the list based on key;
envelop.Replace(match.Value, columnValue);
}
in this, The straight Key,Value pairs are easy to get via Linq but I am getting tough time to fetch the complex values which are nested in terms of connected Key, Value.
is there any way in LINQ or have to go with a loop.
Expected Output : Hello ABC XYZ you have created a request for ActualValueforVAR001 which got assigned to ActualValueforVAR003
Thanks,
PS. The code is not complete. it's a part of entire code edited with an intention to make it concise to issue
Edited: some of my text was not visible due to formatting.
They Dictionary values are nested as I am creating them based on some conditions in which they got configured
First, let's turn initial List<T> into a Dictionary<K, V>:
List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>() {
new KeyValuePair<string, string>("FNAME", "ABC"),
new KeyValuePair<string, string>("LNAME", "XYZ"),
new KeyValuePair<string, string>("VAR001", "VAR002"),
new KeyValuePair<string, string>("VAR002", "ActualValueforVAR001"),
new KeyValuePair<string, string>("VAR003", "VAR004"),
new KeyValuePair<string, string>("VAR004", "VAR005"),
new KeyValuePair<string, string>("VAR005", "ActualValueforVAR003"),
};
Dictionary<string, string> dict = list.ToDictionary(
pair => pair.Key,
pair => pair.Value,
StringComparer.OrdinalIgnoreCase); // Comment out if should be case sensitive
// Some values can be nested
while (true) {
bool nestedFound = false;
foreach (var pair in dict.ToList()) {
if (dict.TryGetValue(pair.Value, out var newValue)) {
dict[pair.Key] = newValue;
nestedFound = true;
}
}
if (!nestedFound)
break;
}
Then for a given envelop
string envelop =
#"Hello [FNAME] [LNAME] you have created a request for [VAR001] which got assigned to [VAR003]";
you can put a simple Regex.Replace:
string result = Regex
.Replace(envelop,
#"\[[A-Za-z0-9]+\]",
m => dict.TryGetValue(m.Value.Trim('[', ']'), out var value) ? value : "???");
Console.Write(result);
Outcome:
Hello ABC XYZ you have created a request for ActualValueforVAR001 which got assigned to ActualValueforVAR003
I have a JSON data ( http://country.io/names.json ) like :
{"BD": "Bangladesh", "BE": "Belgium", "BF": "Burkina Faso", "BG": "Bulgaria", "BA": "Bosnia and Herzegovina", "BB": "Barbados" }
I want to list that json like CountryCode-CountryName (BD-Bangladesh) . How do I do that on windows form app. ?
You could deserialise the JSON into a Dictionary instead of a single object. This gives you access to all the codes and names, like so:
var json = #"{""BD"": ""Bangladesh"", ""BE"": ""Belgium"", ""BF"": ""Burkina Faso"", ""BG"": ""Bulgaria"", ""BA"": ""Bosnia and Herzegovina"", ""BB"": ""Barbados"" }";
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
foreach (var item in dict)
{
var countryCode = item.Key;
var countryName = item.Value;
// do whatever you want to do with those two values here
Console.WriteLine("CountryCode: {0} CountryName: {1}", countryCode, countryName);
}
In that code it simply writes it to the screen, but obviously once you have that loop in place you can do whatever you like with the country code and name.
I have some class with lots of fields;
public class CrowdedHouse
{
public int value1;
public float value2;
public Guid value3;
public string Value4;
// some more fields below
}
My classmust be (de)serialized into simple Windows text file in the following format
NAME1=VALUE1
NAME2=VALUE2
What is the most convinient way to do that in .NET? This is a text file and all the values must be fist converted to string. Let's assume I have already converted all data to strings.
UPDATE One option would be pinvoke WritePrivateProfileString/WritePrivateProfileString
but these are using the required "[Section]" field that I don't need to use.
EDIT: If you have already converted each data value to strings, simply use the method below to serialize it after making a Dictionary of these values:
var dict = new Dictionary<string, string>
{
{ "value1", "value1value" },
{ "value2", "value2value" },
// etc
}
or use dict.Add(string key, string value).
To read the data, simply split each line around the = and store the results as a Dictionary<string, string>:
string[] lines = File.ReadAllLines("file.ext");
var dict = lines.Select(l => l.Split('=')).ToDictionary(a => a[0], a => a[1]);
To convert a dictionary to the file, use:
string[] lines = dict.Select(kvp => kvp.Key + "=" + kvp.Value).ToArray();
File.WriteAllLines(lines);
Note that your NAMEs and VALUEs cannot contain =.
Writing is easy:
// untested
using (var file = System.IO.File.CreateText("data.txt"))
{
foreach(var item in data)
file.WriteLine("{0}={1}", item.Key, item.Value);
}
And for reading it back:
// untested
using (var file = System.IO.File.OpenText("data.txt"))
{
string line;
while ((file.ReadLine()) != null)
{
string[] parts = line.Split('=');
string key = parts[0];
string value = parts[1];
// use it
}
}
But probably the best answer is : Use XML.
Minor improvement of Captain Comic answer:
To enable = in values: (will split only once)
var dict = lines.Select(l => l.Split(new[]{'='},2)).ToDictionary(a => a[0], a => a[1]);