Transfer values between 2 config files - c#

I have a problem, and i can't figure this out myself..
In my program i have an auto updater, when my program updates a new(changed, some new keys) config file is created. what i want my program to do is, is when it's updating to look at both config files(old and new) and transfer old settings that match a key in the new file to the new file.
This is an example of the old file:
{
"Setting1": false,
"Setting2": 123,
"Setting3": "test",
"LocationList": {
"Country": "NL",
"Locations": [
{
"Latitude": 38.556807486461118,
"Longitude": -121.2383794784546
},
{
"Latitude": -33.859019,
"Longitude": 151.213098
},
{
"Latitude": 47.5014969,
"Longitude": -122.0959568
},
{
"Latitude": 51.5025343,
"Longitude": -0.2055027
}
]
}
}
And this can be the new file(can also be different):
{
"Setting1": null,
"Setting2": null,
"Setting3": "",
"Setting4": ""
"LocationList": {
"Country": "",
"Locations": [
{
"Latitude": null,
"Longitude": null
},
{
"Latitude": null,
"Longitude": null
}
]
}
}
Expected result:
{
"Setting1": false,
"Setting2": 123,
"Setting3": "test",
"Setting4": ""
"LocationList": {
"Country": "NL",
"Locations": [
{
"Latitude": 38.556807486461118,
"Longitude": -121.2383794784546
},
{
"Latitude": -33.859019,
"Longitude": 151.213098
},
{
"Latitude": 47.5014969,
"Longitude": -122.0959568
},
{
"Latitude": 51.5025343,
"Longitude": -0.2055027
}
]
}
}
First, i looked at creating a class in c# and just deserialize it, then, i came to the conclusion that this is not possible because i don't know what the config is going to look like.
Second, i thought using a dynamic would do the trick, it didn't, because i didn't knew any keys that were in it. And couldn't figure out how to figure that out.
And lastly, i've looked if it would be possible using regex, for me, this seems impossible..
Can anybody give me some ideas of how they would do it? I don't need code, just a push in the right direction.
P.S. i do not want to combine the two, when there is a key in the old file but not in the new one, it doesn't need to be transferred(Only lists will be completely transferred from the old file, also when the list is empty/filled in the new one).

If you really want to try something in JSON, I can only recommend the excellent JSON.Net library to parse the json for you. Using LINQ to JSON you could easily find matching keys in a recursive fashion between the old config file and the newer one and simply copy the value from one to the other
see documentation and a small example at http://www.newtonsoft.com/json/help/html/LINQtoJSON.htm
briefly you could do so in pseudoCode. Obviously this would not be super performant because you would recursively walk two files a lot of times and could be optimized, but at the same time unless your configuration files are monstrous blobs this should not pose any problem on any kind of modern hardware
//load both config files
//load the first token in original file and walk recursively
//for each token try to match in the new file and write data if required using the same recursive technique to walk the other file

I don't need code, just a push in the right direction.
Store your configuration in a regular App.config file and leverage the System.Configuration. Depending on the configuration complexity you can either use the ready <appSettings> or construct your own config section(s) and elements. It still be easier and error proof than doing custom config.
The matter is too complicated to start inventing the wheel just to use another (text) format. Even if you solve your current problem, there will be more, which are already solved somewhere within the System.Configration...
You can start exploring the configuration options here; anyways a regular search in your favorite search engine will do the trick...

Oke after some trouble i've managed to fix it(Thanks #Louis).
This is how i've done it:
private static bool TransferConfig(string baseDir, ISession session)
{
//if (!session.LogicSettings.TransferConfigAndAuthOnUpdate)
// return false;
var configDir = Path.Combine(baseDir, "Config");
if (!Directory.Exists(configDir))
return false;
var oldConf = GetJObject(Path.Combine(configDir, "config.json.old"));
var oldAuth = GetJObject(Path.Combine(configDir, "auth.json.old"));
GlobalSettings.Load("");
var newConf = GetJObject(Path.Combine(configDir, "config.json"));
var newAuth = GetJObject(Path.Combine(configDir, "auth.json"));
TransferJSON(oldConf, newConf);
TransferJSON(oldAuth, newAuth);
File.WriteAllText(Path.Combine(configDir, "config.json"), newConf.ToString());
File.WriteAllText(Path.Combine(configDir, "auth.json"), newAuth.ToString());
return true;
}
private static JObject GetJObject(string filePath)
{
return JObject.Parse(File.ReadAllText(filePath));
}
private static bool TransferJSON(JObject oldFile, JObject newFile)
{
try
{
foreach (var newProperty in newFile.Properties())
foreach (var oldProperty in oldFile.Properties())
if (newProperty.Name.Equals(oldProperty.Name))
{
newFile[newProperty.Name] = oldProperty.Value;
break;
}
return true;
}
catch
{
return false;
}
}

Related

I can't access the json file in c# Xamarin

I already put the access as being 'read/write' so everyone including me can read and write to the json file. However, the code is still sending out an UnauthorizedAccessException afterwards. This is the code:
public static void read_json(string path)
{
using (StreamReader r = new StreamReader("/Users/steve/Downloads/city_list.json"))
{
string json = r.ReadToEnd();
List<City_Data> items = JsonConvert.DeserializeObject<List<City_Data>>(json);
foreach (City_Data item in items)
{
var key = item.name;
var value = item.id;
dict.Add(key, value);
}
}
}
while this is part of the json file which I have been wanting to access and read from:
[
{
"id": 833,
"name": "Ḩeşār-e Sefīd",
"state": "",
"country": "IR",
"coord": {
"lon": 47.159401,
"lat": 34.330502
}
},
...
]
Make all files writeable when checking out of source control.
Call the Attrib MSBuild task before the transformation to remove the read-only file attribute.
For example:
<Attrib Files="/Users/steve/Downloads/city_list.json" Normal="true"/>
Call the Exec MSBuild task before the transformation to remove the read-only file attribute.
For example:
<Exec Command="attrib -R "/Users/steve/Downloads/city_list.json""/>

Azure function with CosmosDBTrigger doesn't seem to be triggered by upserts

I'm working with Azure Functions for the first time. I'm trying to write a simple function which responds to documents changed or added to a CosmosDb collection. The function I've written looks like this:
[FunctionName("ChangeLog")]
public static void Run([CosmosDBTrigger(
databaseName: "Recaptcha",
collectionName: "Rules",
ConnectionStringSetting = "CosmosDBConnection",
LeaseCollectionName = null)]IReadOnlyList<RuleConfigCollection> documents)
{
if (documents != null && documents.Count > 0)
{
ApplicationEventLogger.Write(
Diagnostics.ApplicationEvents.RecaptchaRulesChanged,
new Dictionary<string, object>()
{
{ "SomeEnrichment", documents[0].Rules.ToList().Count.ToString() }
});
}
}
By my understanding a lease collection is necessary when multiple functions are pointed at the same CosmosDb, but in my case this isn't relevant. That's why I've set the lease collection to null.
I've published this to Azure from Visual Studio and can see the function is created with the following function.json:
{
"generatedBy": "Microsoft.NET.Sdk.Functions-1.0.12",
"configurationSource": "attributes",
"bindings": [
{
"type": "cosmosDBTrigger",
"connectionStringSetting": "CosmosDBConnection",
"collectionName": "Rules",
"databaseName": "Recaptcha",
"leaseDatabaseName": "Recaptcha",
"createLeaseCollectionIfNotExists": false,
"name": "documents"
}
],
"disabled": false,
"scriptFile": "../bin/My.Namespace.Functions.App.dll",
"entryPoint": "My.Namespace.Functions.App.ChangeLogFunction.Run"
}
I've also added an application setting named CosmosDBConnection with the value AccountEndpoint=https://my-cosmosdb.documents.azure.com:443;AccountKey=myAccountKey;.
I run the function then add a document to the collection, but the logs just keep saying No new trace in the past n min(s) and the application events I expect to see are not being written.
Have I missed something in this setup?
I'm not sure that's the root cause of you issue, but your understanding of leaseCollection is wrong.
leaseCollection is used to coordinate multiple instances (workers) of your Function App to distribute partitions between workers.
It is required even for a single Function listening to Cosmos DB change feed.

Load multiple 'nodes' from JSON and store into array

I am currently creating a small Text-Based Game. In this there are obviously multiple rooms, I wish to load those rooms from a JSON file. I am currently doing that as such:
dynamic jRooms = Json.Decode(file);
for (int i = 0; i < Regex.Matches( file, "Room" ).Count; i++){
name[i] = jRooms.Game.Room[i];
description[i] = jRooms.Game.Room.Attributes.Description[i];
exits[i] = jRooms.Game.Room.Attributes.Exits[i];
_count++;
}
That loads information from the following JSON file:
{
'Game': [{
'Room': 'Vault 111 Freeze Chamber',
'Attributes': {
'Description': 'The freeze chamber of the vault you entered after the nuclear fallout.',
'Exits': 'North.Vault 111: Main Hallway'
},
'Room': 'Vault 111 Main Hallway',
'Attributes': {
'Description': 'The main hallway of the vault.',
'Exits': 'South.Vault 111: Freeze Chamber'
}
}]}
This unfortunately throws up an error during run time that I can't seem to work out, which is the following:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot perform runtime binding on a null reference
at CallSite.Target(Closure , CallSite , Object , Int32 )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at TBA.Loader.Rooms()
at TBA.Program.Main(String[] args)
Any help would be greatly appreciated, because I am completely stumped as to what is wrong and not working. If you need anymore of my code, just request it.
Thanks.
The problem is with your JSON. JSON doesn't allow single quotes (maybe they have a different meaning or no meaning at all). Source - W3Schools.
Use services like JSONLint to validate JSON and check for errors. Even JSONLint declares your JSON, invalid. Using double quotes however, it is declared valid. You should use double quotes like this:
{
"Game": [
{
"Room": "Vault111FreezeChamber",
"Attributes": {
"Description": "Thefreezechamberofthevaultyouenteredafterthenuclearfallout.",
"Exits": "North.Vault111: MainHallway"
},
"Room": "Vault111MainHallway",
"Attributes": {
"Description": "Themainhallwayofthevault.",
"Exits": "South.Vault111: FreezeChamber"
}
}
]
}

C# .NET: Deserialize json tree

Good evening,
For my specific problem, I have to create a menu (a tree of menus more exactly). Thus, I decided to use Composite Design Pattern with the following structure:
IMenuComponent (an interface defining some properties and so on)
Menu (contains a list of IMenuComponent)
MenuEntry (a leaf)
So, I'll have to navigate through it while knowing the way back up. The obvious answer is having a 'parent' property.
I have the following json tree:
{
"Guid": "08967257-9306-4717-a76a-e1a4f0050505",
"Parent": null,
"Title": "Main Menu",
"Message": "A sample message",
"Elements": [
{
"$type": "Menu",
"Guid": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Parent": "08967257-9306-4717-a76a-e1a4f0050505",
"Title": "Option 1",
"Message": "Another sample message",
"Elements": [
{
"$type": "MenuEntry",
"Parent": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Title": "Entry 1",
"Message": "Another sample message"
},
{
"$type": "MenuEntry",
"Parent": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Title": "Entry 2",
"Message": "Another sample message"
},
{
"$type": "MenuEntry",
"Parent": "26dfca59-9163-4b11-8033-e8ad13f3f5cc",
"Title": "Entry 3",
"Message": "Another sample message"
}
]
},
{
"$type": "MenuEntry",
"Parent": "08967257-9306-4717-a76a-e1a4f0050505",
"Title": "Option 2",
"Message": "Another sample message"
}
]
}
This isn't about the deserialization itself, since that's working as it should.
My problem is that I'll have to access the parents after deserializing the file to a 'Menu' (the root).
I can think of two ways:
Find a 'parent' in runtime using its Guid - can be bad performance-wise depending on tree size;
Add a 'Menu' property to both 'Menu' and 'MenuEntry' classes to store the 'parent' and fill it, by finding the corresponding Guid, after deserializing from json. Basically, I'd put it together as soon as the application starts, preventing the find in runtime. It could be bad performance-wise as well.
What's the way to go? And how should I do it?
As a side note, I'm using Newtonsoft.Json
Thanks for the help!
#Ian Mercer comment actually helped me figure out a simple solution (using the 2nd way described in my post above).
The 2nd way doesn't really rely on Guids. After the deserialize, I can just iterate over each menu collection (as I probably would anyway) and give the reference to the 'parent' directly.
private void ConnectTree(Menu menu)
{
foreach (IMenuComponent component in menu.Elements) {
if (component is Menu) {
(component as Menu).ParentMenu = menu;
ConnectTree (component as Menu);
}
else if (component is MenuEntry) {
(component as MenuEntry).ParentMenu = menu;
}
}
}
Just need to call ConnectTree(_deserializedMenu)
Edit:
#Brian Rogers just mentioned PreserveReferencesHandling property from json net. The original problem was all about json not supporting circular references, but that solves it - it's the same logic as using Guids. There are multiple solutions, which is always good to know.
Thanks!

Appending new data to an existing JSON file

I have searched high and low, and far and wide for a solution to this, and have spent the last few weeks trying to implement my own solution, but I just can't come up with anything.
I would greatly appreciate any help at all!
I have a file, which looks like, (file.json):
{
"Expense": {
"Name": "OneTel Mobile Bill",
"Amount": "39.90",
"Due": "28/12/2011",
"Recurrence": "1 Months",
"Paid": "0",
"LastPaid": "01/01/2002"
}
}
And in my app, when I create a new 'Expense', I want to append that new Expense to this existing JSON file, so it looks like so:
{
"Expense": {
"Name": "OneTel Mobile Bill",
"Amount": "39.90",
"Due": "28/12/2011",
"Recurrence": "1 Months",
"Paid": "0",
"LastPaid": "01/01/2002"
},
"Expense": {
"Name": "Loan Repayment",
"Amount": "50.00",
"Due": "08/03/2012",
"Recurrence": "3 Months",
"Paid": "0",
"LastPaid": "08/12/2011"
}
}
And this is how I am creating the JSON and writing to the file:
async public void WriteToFile(string type, string data)
{
file = await folder.GetFileAsync(file.FileName);
IRandomAccessStream writestream = await file.OpenAsync(FileAccessMode.ReadWrite);
IOutputStream outputstream = writestream.GetOutputStreamAt(0);
DataWriter datawriter = new DataWriter(outputstream);
datawriter.WriteString(data);
await datawriter.StoreAsync();
outputstream.FlushAsync().Start();
}
private void CreateExpenseButton_Click(object sender, RoutedEventArgs e)
{
//Create the Json file and save it with WriteToFile();
JObject jobject =
new JObject(
new JProperty("Expense",
new JObject(
new JProperty("Name", NameTextBox.Text),
new JProperty("Amount", AmountTextBox.Text),
new JProperty("Due", DueTextBox.Text),
new JProperty("Recurrence", EveryTextBox.Text + " " + EveryComboBox.SelectionBoxItem),
new JProperty("Paid", "0"),
new JProperty("LastPaid", "Never")
)
)
);
try
{
WriteToFile(Expenses, jobject.ToString());
// Close the flyout now.
this.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
catch (Exception exception)
{
Debug.Write(exception.Message);
}
}
I am using the Json.NET library from James Newton King, and it's pretty awesome but even after reading the included documentation, I have absolutely no idea how to read the JSON file and append data to it.
Are there any samples around that demonstrate how this is done, or can you recommend another library for C# that would allow me to accomplish this?
Edit
This is how I am reading a single expense from the json file:
JObject json = JObject.Parse(data);
Expense expense = new Expense
{
Amount = (string)json["Expense"]["Amount"],
Due = (string)json["Expense"]["Due"],
Name = (string)json["Expense"]["Name"]
};
Debug.Write(expense.Amount);
You can try to deserialize the data in to a object Expence and add your data, then serialize the object (list of objects) to file.
Since you can read directly into an Expense object, you should be able to add such an object to a List<Expense> - add the new data as an Expense object to the list (directly from your form data, or otherwise).
At this point you should be able to write out the List<Expense> out using JSON.NET - it should take care of creating the list.
I suggest you always save a List<Expense>, even if it contains only one item as it would make serializing and deserializing easier.

Categories

Resources