How to save different classes in the same file - c#

So I have downloaded an asset for Unity called "JSON .NET For Unity", and I got it working, but I have a problem.
I have different classes and variables and I want to store them, the problem its that I dont know how to save them in the same file. I have a different method for each thing that I want to save but I dont know how to do that in the same method or making it write in the same file.
This is one example: In this method I save the class named World and I get it from the file. I have other methods like this one that asks for different things (a list, a variable...)
public void SaveWorld(World worldToSave)
{
SaveSystem.Init();
string json = JsonConvert.SerializeObject(worldToSave, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
File.WriteAllText(SaveSystem.SAVE_FOLDER + "/Save.json", json);
}
public World LoadWorld()
{
World saveWorld = null;
if (File.Exists(SaveSystem.SAVE_FOLDER + "/Save.json"))
{
string saveString = File.ReadAllText(SaveSystem.SAVE_FOLDER + "/Save.json");
saveWorld = JsonConvert.DeserializeObject<World>(saveString, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});
}
return saveWorld;
}
Then for saving I would call this:
void Save()
{
saveLoadSystem.SaveWorld(worldHandler.World);
saveLoadSystem.SaveInstalledObjects(worldHandler.installedObjectList);
saveLoadSystem.SaveUnits(unitHandler.unitList);
}
Where saveLoadSystem is the script that has all the methods.
Thank you
Edit: Using Hacettepe Hesabı answer.
I can save correctly like he said and saving a class that only contains an int called numData and another script with a List of ints I get this:
{
"$id": "1",
"numData": 2
}[
0,
1
]
The problem is that when I load I get this error:
JsonReaderException: Additional text encountered after finished reading JSON content: [. Path '', line 4, position 1.
For loading Im using the next method:
public DATA LoadData()
{
DATA saveData = null;
if (File.Exists(SaveSystem.SAVE_FOLDER + "/Save.json"))
{
string saveString = File.ReadAllText(SaveSystem.SAVE_FOLDER + "/Save.json");
saveData = JsonConvert.DeserializeObject<DATA>(saveString, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});
}
return saveData;
}

To save them all to the same file you could have the three instances in the same class and then save that class.

Related

Update property in json file with C#

I'm looking to change a specific property for each json record in my json file. I'd like to change the "Completed" property to "true" when a method finishes executing.
My json file looks like:
{
"LoanRecords": [
{
"LoanGUID": "{70dbec7e-5e94-460d-831c-0a5dc2d085e2}",
"RecordDT": "2020-11-10T14:44:34.378Z",
"Completed": "false",
"Environment": "TEBE",
"ProcessType": "RateLock"
},
{
"LoanGUID": "{70dbec7e-5e94-460d-831c-0a5dc2d085e2}",
"RecordDT": "2020-11-10T14:53:12.187Z",
"Completed": "false",
"Environment": "TEBE",
"ProcessType": "RateLock"
}
]
}
My C# code is the following:
private void ExecuteEvent(object sender, ElapsedEventArgs e)
{
string fileRecord = File.ReadAllText(jsonfile);
LoanRecordRoot LoanRecord = JsonConvert.DeserializeObject<LoanRecordRoot>(fileRecord);
foreach (var rec in LoanRecord.LoanRecords)
{
if (rec.Completed == "false")
{
bool recordModified = ManipulateEncompass(rec.LoanGUID, rec.ProcessType);
if (recordModified)
{
// What should I do here to update "rec.Completed" to "true"
// for this particular record and write it back to the json file
// for that specific entry?
}
}
}
Console.WriteLine("Successfully manipulated records!");
}
Is there a way to flip the "Completed" property to "true" for the specific record in my "foreach" iteration, and update the json file accordingly for that specific record? I am hoping to avoid reading the entire file, deserializing, changing the property then writing the entire content back to the json file, I'm looking to just flip that specific property for each record in my "foreach" loop. -- I hope that makes sense.
I've looked at similar questions, which seem close to what I'm looking for, but the examples I've seen don't reflect writing back to the json file specifically without overwriting the file contents -- unless this specific action isn't possible, or I'm failing to understand the entire process (highly possible.)
Ex of a solution that's close to what I'm looking for: How to update a property of a JSON object using NewtonSoft -- but doesn't seem to quite fit the bill for what I'm wanting to do.
Thank you in advance for any helpful leads!
you need to save the complete JSON when you update a property of an element of the array
static void Main(string[] args)
{
const string jsonPath = #"C:\Logs\recordRoot.json";
var loanRecordRoot = JsonConvert.DeserializeObject<LoanRecordRoot>(File.ReadAllText(jsonPath));
foreach (var record in loanRecordRoot.LoanRecords)
{
if (record.Completed == "false")
{
if (ManipulateEncompass(rec.LoanGUID, rec.ProcessType))
{
record.Completed = "true";
}
}
}
//Save Json
var json = JsonConvert.SerializeObject(loanRecordRoot, Formatting.Indented);
File.WriteAllText(jsonPath, json);
}
Looking at your JSON, it appears the "Completed" property is being serialized as of type string
Therefore, all you need to do is set it to "Completed": "true" within your condition in your snippet.
if (recordModified)
{
rec.Completed = "true";
}
At the end of your processing, simply serialize your LoanRecord object and write it back to your file.
using Kitchen_Mini_Project.Constants;
using Kitchen_Mini_Project.Moduls;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Kitchen_Mini_Project.Services
{
public class Update
{
public static void UpdateAnyProduct()
{
string readdedFile = File.ReadAllText(PathConst.ProductDBPath);
IList<Restaurant> products = JsonConvert.DeserializeObject<IList<Restaurant>>(readdedFile);
foreach (var product in products[0].FoodItems)
{
if (product.foodName == "Chicken Burrito")
{
product.foodName = "Chicken Burrito is Update ha ha";
}
}
var json = JsonConvert.SerializeObject(products, Formatting.Indented);
File.WriteAllText(PathConst.ProductDBPath, json);
}
}
}
1-install package Newtonsoft.Json
https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-visual-studio
2-use
string json = File.ReadAllText("server_client _input.json");
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
jsonObj["Bots"][0]["Password"] = "new password";
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
File.WriteAllText("settings.json", output);
(For Install package use of this page :https://www.newtonsoft.com/json)

Cannot deserialize the current JSON array (e.g. [1,2,3]) & {"Unexpected character encountered while parsing value: e. Path '', line 0, position 0."}

I saw plenty of similar questions but I really could not work my way around this time. What I am having issues with, is populating a view with the content of a certain object in order to update it. When I press on "Edit", in theory, all the fields should be automatically inserted. What have I tried so far?
public ActionResult CreateOrEdit(int id = 0)
{
if (id==0)
return View(new Recipe());
else
{
HttpResponseMessage response = GlobalVariables.client.GetAsync(id.ToString()).Result;
//return ViewJsonConvert.DeserializeObject<IList<Recipe>>(response.ToString()));
return View(response.Content.ReadAsAsync<Recipe>().Result);
//return View(new Recipe());
}
}
return View(response.Content.ReadAsAsync<Recipe>().Result); - when using this as a return, I am getting this error:
Cannot deserialize the current JSON array (e.g. [1,2,3])
After looking the problem up, I tried the following:
return View(JsonConvert.DeserializeObject<IList<Recipe>>(response.ToString())); which got me this error:
{"Unexpected character encountered while parsing value: s. Path '', line 0, position 0."}
And at this point I am stuck. I would assume that it is trying to deserialize the following JSON:
{
"id": 5002,
"name": "Test Recipe",
"recipeLink": "testlink",
"category1Id": 7757,
"category2Id": 7758,
"category3Id": 7759,
"category4Id": 7760,
"recipeById": 1,
"totalTime": 30,
"totalTimeUnitId": 1,
"activeTime": 20,
"activeTimeUnitId": 1,
"instructions": "Test Instructions",
"sourceKey": "Test SK",
"recipeBy": "TestPerson",
"insertedAtUtc": "2019-09-04T12:18:48.0466667",
"isVerified": 1,
"numPersons": 5
}
If needed, here is the code from the API Controller that is handling the operations.
[Route("v1/recipe/{id}")]
[HttpPut()]
public IActionResult UpdateList(int id, [FromBody]Recipe recipe)
{
var category1Id = 7757;
var category2Id = 7758;
var category3Id = 7759;
var category4Id = 7760;
var isVerified = 0;
var recipeBy = "TestPerson";
var recipeById = 1;
try
{
if (recipe == null) throw new ArgumentException("No data specified");
//if (newData.Name == null) throw new ArgumentException("No name specified");
using (var con = _connFactory())
{
con.Execute(#"UPDATE dbo.Recipe SET Name=#name, RecipeLink=#recipeLink, Category1Id=#category1Id ,Category2Id=#category2Id,
Category3Id=#category3Id, Category4Id=#category4Id, RecipeById=#recipeById, TotalTime=#totalTime, TotalTimeUnitId=#totalTimeUnitId,
ActiveTime=#activeTime, ActiveTimeUnitId=#activeTimeUnitId, Instructions=#instructions, SourceKey=#sourceKey, RecipeBy=#recipeBy,
InsertedAtUtc=getutcdate(), IsVerified=#isVerified, NumPersons=#numPersons WHERE Id=#id",
new
{
id,
recipe.name,
recipe.recipeLink,
category1Id,
category2Id,
category3Id,
category4Id,
recipeById,
recipe.totalTime,
recipe.totalTimeUnitId,
recipe.activeTime,
recipe.activeTimeUnitId,
recipe.instructions,
recipe.sourceKey,
recipeBy,
isVerified,
recipe.numPersons
});
}
return Ok(recipe);
}
catch (Exception exc)
{
return BadRequest();
}
}
I believe your response string starts with the Unicode Byte Order Mark character.
For example...
using Newtonsoft.Json;
namespace StackOverflow
{
class MainClass
{
public static void Main(string[] args)
{
var json = #"{
'id': 5002,
'name': 'Test Recipe',
'recipeLink': 'testlink',
'category1Id': 7757,
'category2Id': 7758,
'category3Id': 7759,
'category4Id': 7760,
'recipeById': 1,
'totalTime': 30,
'totalTimeUnitId': 1,
'activeTime': 20,
'activeTimeUnitId': 1,
'instructions': 'Test Instructions',
'sourceKey': 'Test SK',
'recipeBy': 'TestPerson',
'insertedAtUtc': '2019-09-04T12:18:48.0466667',
'isVerified': 1,
'numPersons': 5
}".Replace("'", "\"");
//This works...
var deserialized1 = JsonConvert.DeserializeObject(json);
//Prepend a U+FEFF Byte Order Mark character...
json = "\uFEFF" + json;
//This fails with error:
//Newtonsoft.Json.JsonReaderException:
//Unexpected character encountered while parsing value: . Path '', line 0, position 0.
var deserialized2 = JsonConvert.DeserializeObject(json);
}
}
}
I wonder if you have a string value where it is expecting an integer, i.e. a character which is not wrapped in quotation marks.
Could you debug to the line JsonConvert.DeserializeObject<IList<Recipe>>(response.ToString())); in Visual Studio and then inspect the contents of "response" in the locals window and post a screenshot to help diagnose the issue?
Edit in response to comment:
Try adding these steps in before the return, then see what is contained in the temp variable:
string temp = response.Content.ReadAsAsync<string>().Result;
It will be interesting to see what this holds, maybe then it will show why the data cannot be parsed into a Recipe object.
Further edit
OK, instead of that line that won't execute try adding this code:
Stream receiveStream = response.GetResponseStream();
StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
var temporaryData = readStream.ReadToEnd();
See if that will execute and then inspect the contents of tempoararyData variable.
Edit 3
OK great we can see the data now. I believe the issue is it is coming through as a JSON array and you're trying to deserialize a singular version. I made this demo app to illustrate:
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
string data = "[{\"Id\":5002,\"Name\":\"Test Recipe\"}]";
string data2 = "{\"Id\":5002,\"Name\":\"Test Recipe\"}";
//This throws an exception
//DataClass account = JsonConvert.DeserializeObject<DataClass>(data);
//This works
DataClass account2 = JsonConvert.DeserializeObject<DataClass>(data2);
//This also works
DataClass[] account3 = JsonConvert.DeserializeObject<DataClass[]>(data);
}
}
class DataClass
{
public int Id { get; set; }
public string Name { get; set; }
}
}
I believe if you change your code to deserialize an array i.e. DeserializeObject<IList<Recipe[]>> hopefully it will work

Adding object to JSON file

I'm creating a software on which I added a profiles feature where the user can create profile to load his informations faster. To store these informations, I'm using a JSON file, which contains as much objects as there are profiles.
Here is the format of the JSON file when a profile is contained (not the actual one, an example) :
{
"Profile-name": {
"form_email": "example#example.com",
//Many other informations...
}
}
Here is the code I'm using to write the JSON and its content :
string json = File.ReadAllText("profiles.json");
dynamic profiles = JsonConvert.DeserializeObject(json);
if (profiles == null)
{
File.WriteAllText(jsonFilePath, "{}");
json = File.ReadAllText(jsonFilePath);
profiles = JsonConvert.DeserializeObject<Dictionary<string, Profile_Name>>(json);
}
profiles.Add(profile_name.Text, new Profile_Name { form_email = form_email.Text });
var newJson = JsonConvert.SerializeObject(profiles, Formatting.Indented);
File.WriteAllText(jsonFilePath, newJson);
profile_tr.Nodes.Add(profile_name.Text, profile_name.Text);
debug_tb.Text += newJson;
But when the profiles.json file is completely empty, the profile is successfully written, but when I'm trying to ADD a profile when another one already exists, I get this error :
The best overloaded method match for 'Newtonsoft.Json.Linq.JObject.Add(string, Newtonsoft.Json.Linq.JToken)' has some invalid arguments on the profiles.Add(); line.
By the way, you can notice that I need to add {} by a non-trivial way in the file if it's empty, maybe it has something to do with the error ?
The expected output would be this JSON file :
{
"Profile-name": {
"form_email": "example#example.com",
//Many other informations...
},
"Second-profile": {
"form_email": "anotherexample#example.com"
//Some other informations...
}
}
Okay so I found by reading my code again, so I just replaced dynamic profiles = JsonConvert.DeserializeObject(json); to dynamic profiles = JsonConvert.DeserializeObject<Dictionary<string, Profile_Name>>(json);.
But it still don't fix the non-trivial way I use to add the {} to my file...
The object the first DeserializeObject method returns is actually a JObject, but below you deserialize it as a Dictionary. You shouldn't be mixing the types, choose either one.
If you use the JObject then to add objects you need to convert them to JObjects:
profiles.Add(profile_name.Text, JObject.FromObject(new Profile_Name { form_email = form_email.Text }));
In both cases, when the profile is null you just need to initialize it:
if (profiles == null)
{
profiles = new JObject(); // or new Dictionary<string, Profile_Name>();
}

Use Log4net to generate json format log with custom layout/fields?

can we use Log4net to generate json format log with custom layout/fields?
I'm using log4net to log some information. Now, because of this and that, we need to log it as json format.
I'm using log4net.Ext.Json for this, it logs the information like this:
{"date":"2018-10-29T15:18:26.7785983-07:00","level":"INFO","logger":"Service.Services.LogService","message":"data_length: 10"}
{"date":"2018-10-29T15:18:26.7796462-07:00","level":"INFO","logger":"Service.Services.LogService","message":"max_parallelism: 1"}
However, since we will log lots of information, and we will feed this log to another program to analyze. So, we want to output it like:
{
"_index": "error_201843",
"_type": "error_web",
"_id": "AWaytV_hi121qded",
"_version": 1,
"_source": {
"ApplicationSource": "Data Feed",
"ErrorType": "RequestTimeout",
"XStackTrace": "",
"ErrorMessageText": ""
}
}
_index, _typem _id, _version are constant. _source data comes from the actual log fields.
How can we do this? Any ideas? I'm thinking of have a method to build the entire string, then output the string. But it should have a better way to do this, I think.
Thanks
We have been doing this for some time and can be done using a dynamic object. We have different log event types which are centralized and this allows the flexibility to add whatever information required. The loggingEvent event object is from log4net and an Append override in a custom appender.
JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore };
dynamic m = new System.Dynamic.ExpandoObject();
m.SessionId = _sessionId;
m.ProcessId = _processId.ToString();
m.ProcessName = _processName;
m.MachineName = _machineName;
m.UserName = _userName;
m.Level = loggingEvent.Level.DisplayName;
m.Domain = loggingEvent.Domain;
m.TimeStamp = loggingEvent.TimeStamp.ToString("yyyyMMddHHmmssfff");
m.MessageObject = loggingEvent.MessageObject;
if (loggingEvent.ExceptionObject != null)
{
m.Exception = loggingEvent.ExceptionObject.ToString();
m.StackTrace = loggingEvent.ExceptionObject.StackTrace;
}
//Convert the object to a json string
string msg = JsonConvert.SerializeObject(m, Formatting.None, _jsonSerializerSettings);

Deserializing abstract Queue<T> in Json [duplicate]

This question already has an answer here:
Deserialize a List<AbstractClass> with newtonsoft.json
(1 answer)
Closed 7 years ago.
I have a Queue of an abstract class KVP. I queue 2 different objects which inherit from KVP. Everything works fine when I serialize the queue, but since KVP cannot be constructed it fails on deserialization.
If it was a single non generic object I could deserialize as dynamic, but I'm not sure how to deserialize a queue that could hold both events and IDs.
Sample code:
public virtual async Task<bool> LoadFromFile(string FileName, bool addToExistingQueue,bool DeleteFileAfterLoad = true)
{
try
{
IFile File = await PCLStorage.FileSystem.Current.LocalStorage.GetFileAsync(FileName);
var serializedText = await File.ReadAllTextAsync();
var mQueue = JsonConvert.DeserializeObject<Queue<T>>(serializedText,jss);
if (!addToExistingQueue)
{
_queue = new ConcurrentQueue<T>();
}
while (mQueue.Count > 0)
{
_queue.Enqueue(mQueue.Dequeue());
}
if (DeleteFileAfterLoad)
{
await File.DeleteAsync();
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine("Could not load File. Exception Message: " + ex.Message);
return false;
}
}
public virtual async Task<bool> WriteToFile(string FileName)
{
try
{
Debug.WriteLine("Writing File: " + FileName);
var File = await FileSystem.Current.LocalStorage.CreateFileAsync(FileName, CreationCollisionOption.ReplaceExisting);
var serializedText = JsonConvert.SerializeObject(_queue.ToList(),jss);
await File.WriteAllTextAsync(serializedText);
return true;
}
catch (Exception ex)
{
Debug.WriteLine("Could not write File with exception message: " + ex.Message);
return false;
}
}
You could
Enable TypeNameHandling (in both serialization and deserialization):
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var serializedText= JsonConvert.SerializeObject(mQueue, settings);
And then later
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var mQueue = JsonConvert.DeserializeObject<Queue<T>>(serializedText, settings);
This adds an extra "$type" property to your polymorphic classes, as is described here.
Before choosing this solution, for a discussion of possible security concerns using TypeNameHandling, see TypeNameHandling caution in Newtonsoft Json and How to configure Json.NET to create a vulnerable web API.
Write a custom converter that looks at the actual properties and chooses which derived class to use, as is discussed here: Deserializing polymorphic json classes without type information using json.net. This avoids the need for the extra "$type" property.
This should work if you use the following setting in the JSON.NET serialiser settings:
var settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto};
This will encode the object type into the JSON stream, which will help the deserializer determined the specific object type to construct during de-serialization.

Categories

Resources