Error while deserializing to json an unzipped string - c#

I create a zip file and I copy in it a file that contains a serialized list of objects. The file encoding is in UTF8. Then I unzip the file and I try to deserialize it, but I will get this error:
Unexpected character encountered while parsing value: . Path '', line 0, position 0
The problem does not exist if I use ASCII encoding instead of UTF8. But I need to use the UTF8. So I am wondering if the DotNetZip library does not have full support for the UTF8, or maybe I am missing something else.
In order to reproduce the error:
Json library is the one at: http://json.codeplex.com/
The Zip library is the one at: http://dotnetzip.codeplex.com/
Create a simple class "Dog":
public class Dog
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Then use this code (the last line will cause the error):
var list = new List<Dog>();
list.Add(new Dog { FirstName = "Arasd", LastName = "1234123" });
list.Add(new Dog { FirstName = "fghfgh", LastName = "vbnvbn" });
var serialized = JsonConvert.SerializeObject(list, Formatting.Indented);
var zipFile = new ZipFile(#"C:\Users\daviko\Desktop\test.zip");
using (zipFile)
{
zipFile.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
zipFile.UpdateEntry("dogs.txt", serialized, UTF8Encoding.UTF8);
zipFile.Save();
}
var readFromZipFile = string.Empty;
using (var input = new MemoryStream())
{
using (zipFile)
{
var entry = zipFile["dogs.txt"];
entry.Extract(input);
}
using (var output = new MemoryStream())
{
input.CopyTo(output);
readFromZipFile = new UTF8Encoding().GetString( input.ToArray());
}
}
var deserialized = JsonConvert.DeserializeObject<List<Dog>>(readFromZipFile);

The following code:
using (zipFile)
{
zipFile.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;
zipFile.UpdateEntry("dogs.txt", serialized, UTF8Encoding.UTF8);
zipFile.Save();
}
will dispose the zipFile when it executes. So you must create the zipFile again, before your try reading it again.

Related

Clone a JsonNode and attach it to another one in .NET 6

I'm using System.Text.Json.Nodes in .NET 6.0 and what I'm trying to do is simple: Copy a JsonNode from one and attach the node to another JsonNode.
The following is my code.
public static string concQuest(string input, string allQuest, string questId) {
JsonNode inputNode = JsonNode.Parse(input)!;
JsonNode allQuestNode = JsonNode.Parse(allQuest)!;
JsonNode quest = allQuestNode.AsArray().First(quest =>
quest!["id"]!.GetValue<string>() == questId) ?? throw new KeyNotFoundException("No matching questId found.");
inputNode["quest"] = quest; // Exception occured
return inputNode.ToJsonString(options);
}
But when I try to run it, I got a System.InvalidOperationException said "The node already has a parent."
I've tried edit
inputNode["quest"] = quest;
to
inputNode["quest"] = quest.Root; // quest.Root is also a JsonNode
Then the code runs well but it returns all nodes instead of the one I specified which is not the result I want. Also since the code works fine, I think it is feasible to set a JsonNode to another one directly.
According to the exception message, it seems if I want to add a JsonNode to another one, I must unattach it from its parent first, but how can I do this?
Note that my JSON file is quite big (more than 6MB), so I want to ensure there are no performance issues with my solution.
Easiest option would be to convert json node into string and parse it again (though possibly not the most performant one):
var destination = #"{}";
var source = "[{\"id\": 1, \"name\":\"some quest\"},{}]";
var sourceJson = JsonNode.Parse(source);
var destinationJson = JsonNode.Parse(destination);
var quest = sourceJson.AsArray().First();
destinationJson["quest"] = JsonNode.Parse(quest.ToJsonString());
Console.WriteLine(destinationJson.ToJsonString(new() { WriteIndented = true }));
Will print:
{
"quest": {
"id": 1,
"name": "some quest"
}
}
UPD
Another trick is to deserialize the JsonNode to JsonNode:
...
var quest = sourceJson.AsArray().First();
var clone = quest.Deserialize<JsonNode>();
clone["name"] = "New name";
destinationJson["quest"] = clone;
Console.WriteLine(quest["name"]);
Console.WriteLine(destinationJson.ToJsonString(new() { WriteIndented = true }));
Prints:
some quest
{
"quest": {
"id": 1,
"name": "New name"
}
}
As JsonNode has no Clone() method as of .NET 6, the easiest way to copy it is probably to invoke the serializer's JsonSerializer.Deserialize<TValue>(JsonNode, JsonSerializerOptions) extension method to deserialize your node directly into another node. First, introduce the following extension methods to copy or move a node:
public static partial class JsonExtensions
{
public static TNode? CopyNode<TNode>(this TNode? node) where TNode : JsonNode => node?.Deserialize<TNode>();
public static JsonNode? MoveNode(this JsonArray array, int id, JsonObject newParent, string name)
{
var node = array[id];
array.RemoveAt(id);
return newParent[name] = node;
}
public static JsonNode? MoveNode(this JsonObject parent, string oldName, JsonObject newParent, string name)
{
parent.Remove(oldName, out var node);
return newParent[name] = node;
}
public static TNode ThrowOnNull<TNode>(this TNode? value) where TNode : JsonNode => value ?? throw new JsonException("Null JSON value");
}
Now your code may be written as follows:
public static string concQuest(string input, string allQuest, string questId)
{
var inputObject = JsonNode.Parse(input).ThrowOnNull().AsObject();
var allQuestArray = JsonNode.Parse(allQuest).ThrowOnNull().AsArray();
concQuest(inputObject, allQuestArray, questId);
return inputObject.ToJsonString();
}
public static JsonNode? concQuest(JsonObject inputObject, JsonArray allQuestArray, string questId)
{
// Enumerable.First() will throw an InvalidOperationException if no element is found satisfying the predicate.
var node = allQuestArray.First(quest => quest!["id"]!.GetValue<string>() == questId);
return inputObject["quest"] = node.CopyNode();
}
Alternatively, if you aren't going to keep your array of quests around, you could just move the node from the array to the target like so:
public static string concQuest(string input, string allQuest, string questId)
{
var inputObject = JsonNode.Parse(input).ThrowOnNull().AsObject();
var allQuestArray = JsonNode.Parse(allQuest).ThrowOnNull().AsArray();
concQuest(inputObject, allQuestArray, questId);
return inputObject.ToJsonString();
}
public static JsonNode? concQuest(JsonObject inputObject, JsonArray allQuestArray, string questId)
{
// Enumerable.First() will throw an InvalidOperationException if no element is found satisfying the predicate.
var (_, index) = allQuestArray.Select((quest, index) => (quest, index)).First(p => p.quest!["id"]!.GetValue<string>() == questId);
return allQuestArray.MoveNode(index, inputObject, "quest");
}
Also, you wrote
since my json file is quite big (more than 6MB), I was worried there might be some performance issues.
In that case I would avoid loading the JSON files into the input and allQuest strings because strings larger than 85,000 bytes go on the large object heap which can cause subsequent performance degradation. Instead, deserialize directly from the relevant files into JsonNode arrays and objects like so:
var questId = "2"; // Or whatever
JsonArray allQuest;
using (var stream = new FileStream(allQuestFileName, new FileStreamOptions { Mode = FileMode.Open, Access = FileAccess.Read }))
allQuest = JsonNode.Parse(stream).ThrowOnNull().AsArray();
JsonObject input;
using (var stream = new FileStream(inputFileName, new FileStreamOptions { Mode = FileMode.Open, Access = FileAccess.Read }))
input = JsonNode.Parse(stream).ThrowOnNull().AsObject();
JsonExtensions.concQuest(input, allQuest, questId);
using (var stream = new FileStream(inputFileName, new FileStreamOptions { Mode = FileMode.Create, Access = FileAccess.Write }))
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
input.WriteTo(writer);
Or, if your app is asynchronous, you can do:
JsonArray allQuest;
await using (var stream = new FileStream(allQuestFileName, new FileStreamOptions { Mode = FileMode.Open, Access = FileAccess.Read, Options = FileOptions.Asynchronous }))
allQuest = (await JsonSerializer.DeserializeAsync<JsonArray>(stream)).ThrowOnNull();
JsonObject input;
await using (var stream = new FileStream(inputFileName, new FileStreamOptions { Mode = FileMode.Open, Access = FileAccess.Read, Options = FileOptions.Asynchronous }))
input = (await JsonSerializer.DeserializeAsync<JsonObject>(stream)).ThrowOnNull();
JsonExtensions.concQuest(input, allQuest, questId);
await using (var stream = new FileStream(inputFileName, new FileStreamOptions { Mode = FileMode.Create, Access = FileAccess.Write, Options = FileOptions.Asynchronous }))
await JsonSerializer.SerializeAsync(stream, input, new JsonSerializerOptions { WriteIndented = true });
Notes:
Microsoft's documentation explicitly recommends against serializing from and to strings instead of UTF-8 byte sequences for performance reasons which is another reason not to load your large JSON files into temporary string buffers.
Demo fiddles:
For copying the node, see https://dotnetfiddle.net/cwKDen.
For moving the node, see https://dotnetfiddle.net/cI8DuB.
For async reading and writing, see https://dotnetfiddle.net/VjKstQ

c# .netcore Get ImageURL of all files under amazon s3 folder

Using C# and amazon .Net core, able to list all the files URL path with in a amazon S3 folder as below:
public async Task<string> GetMenuUrl(entities.Restaurant restaurant)
{
AmazonS3Client s3Client = new AmazonS3Client(_appSettings.AWSPublicKey, _appSettings.AWSPrivateKey, Amazon.RegionEndpoint.APSoutheast2);
string imagePath;
int restaurantId = restaurant.RestaurantId;
ListObjectsRequest listRequest = new ListObjectsRequest();
ListObjectsResponse listResponse;
imagePath = $"Business_menu/{restaurantId}/";
listRequest.BucketName = _appSettings.AWSS3BucketName;
listRequest.Prefix = imagePath;
do
{
listResponse = await s3Client.ListObjectsAsync(listRequest);
} while (listResponse.IsTruncated);
var files = listResponse.S3Objects.Select(x => x.Key);
var fileName = files.Select(x => Path.GetFileName(x)).ToList(); //outputs Test.jpg, Test2.jpg on this line
var fileNameJoin = string.Join(", ", fileName);
var result = fileNameJoin.Split(); // contains 2 files Test.jpg, Test2.jpg
//Need AWSS3BucketUrl for all files in imagePath
string imageUrl = $"{_appSettings.AWSS3BucketUrl}{imagePath}{result}";
return imageUrl;
}
public async Task<MenuResponse> GetVenueMenuUrl(int restaurantId)
{
var restaurant = await _context.Restaurant.Where(w => w.RestaurantId == restaurantId).FirstOrDefaultAsync();
var result = await _Service.GetMenuUrl(restaurant);
var response = new MenuResponse()
{
MenuUrl = result
};
return response;
}
I want to return each imageurl as the following:
{
menuUrl : "https://...bucketpath../test.jpg"
},
{
menuUrl: "https://...bucketpath../test2.jpg"
},
What Is currently being outputted:
{
"menuUrl": "https://..bucketpath../System.String[]"
}
It should be outputting two imageUrls not one and also it seems to be outputting the list type not the content.
string imageUrl = $"{_appSettings.AWSS3BucketUrl}{imagePath}{result}";
You're telling C# to concatenate a string with "result", but "result" is not a string, it's an array. To tell the compiler what you really want, you need to get the individual strings out of the array and use those instead.
It also looks like you're converting your result into JSON in code we can't see. If that's the case, you could try returning a list of objects from your function instead of a string, which will then get serialized into something a lot closer to your expected output:
// make a simple class that will be serialized cleanly
public class MenuURL
{
public string menuUrl;
}
// .... back in your function ....
List<MenuURL> URLs = new List<MenuURL>();
// Loop through your "results" variable
foreach (string str in result)
{
MenuURL url = new MenuURL()
{
menuUrl = $"{_appSettings.AWSS3BucketUrl}{imagePath}{str}"
};
URLs.Add(url);
}
return URLs;
When you convert the list you get back into JSON, it should look more like what you want, ie:
[
{
"menuUrl" : "https://...bucketpath../test.jpg"
},
{
"menuUrl": "https://...bucketpath../test2.jpg"
},
]

How to read and write a list of users in a text file

Hello I'm trying to learn C# and although I have looked I haven't found a way of solving my problem.
I have a Users class with a Username, Password, Score.
And all of them have setters and getters.
What I'm trying to do is:
After I create my List : ListOfUsers I wanna save all of the users of that list in a single text file (Login.txt)
I tried this:
Users user = new Users(username, password, score);
ListOfUsers.Add(user);
StreamWriter sw = new StreamWriter("Login.txt", true);
sw.WriteLine(ListOfUsers);
sw.Close();
I think that I have passed the list into my file. But I can't seem to find a way of reading all the users from the file.
Thank you for your time!
If you want to save to a text file, at least make it an Xml file, instead of a flat file. The code below adds users to a list, saves it as an Xml, clears the list, and then reads from the file.
The Xml file looks like this:
<ArrayOfUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<User Name="A" Password="AAA" Score="100" />
<User Name="B" Password="BBB" Score="87" />
<User Name="C" Password="CCC" Score="52" />
</ArrayOfUser>
And the sample source code
public class User
{
public User()
{
this.Name= string.Empty;
this.Password = string.Empty;
this.Score= 0;
}
public User(string name, string password, int score)
{
Name=name;
Password=password;
Score=score;
}
[XmlAttribute] public string Name { get; set; }
[XmlAttribute] public string Password { get; set; }
[XmlAttribute] public int Score { get; set; }
}
class Program
{
static void Main(string[] args)
{
var list = new List<User>();
list.Add(new User("A", "AAA", 100));
list.Add(new User("B", "BBB", 87));
list.Add(new User("C", "CCC", 52));
SaveToXml(list.ToArray(), "Login.xml");
list.Clear();
list.AddRange(ReadFromXml("Login.xml"));
foreach (var item in list)
{
Console.WriteLine($"{item.Name}, {item.Score}");
}
}
static void SaveToXml(User[] users, string fileName)
{
var xs = new XmlSerializer(typeof(User[]));
var settings = new XmlWriterSettings()
{
Indent = true,
OmitXmlDeclaration = true,
};
var xw = XmlWriter.Create(fileName, settings);
xs.Serialize(xw, users);
xw.Close();
}
static User[] ReadFromXml(string fileName)
{
var xs = new XmlSerializer(typeof(User[]));
var xr = XmlReader.Create(fileName);
return xs.Deserialize(xr) as User[];
}
}
As Vladimir told, better don't save any login data to a text file (even better don't save it at all).
In any way, if it's something you have to do for any reason, you can look here: here
After that, I suggest you to encrypt the file, so nobody can read the content except the user that creates the file itself: How to crypt/decrypt file
I did something like that, some time ago. Here's the example.
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Archivo de texto (*.txt)|*.txt";
saveFileDialog.FileName = $"Compras_Auxiliar_{(int)Periodo.Value}{_ejercicioContableManager.SearchById(_idEjercicioContable).Anio}_{_empresaManager.SearchById(_idEmpresa).Nit}.txt";
saveFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
List<BancarizacionCompra> listaBancarizacionCompra = _bancarizacionComprasManager.ObtenerTodo.Where(bancarizacion => bancarizacion.EsProyecto == false).ToList();
string[] lines = new string[listaBancarizacionCompra.Count()];
for (int i = 0; i < listaBancarizacionCompra.Count(); i++)
{
lines[i] = $"{(int)listaBancarizacionCompra[i].ModalidadTransaccion}|{listaBancarizacionCompra[i].FechaDocumentoPago.ToShortDateString()}|{(int)listaBancarizacionCompra[i].TipoTransaccion}|{listaBancarizacionCompra[i].CiNitProveedor}|{listaBancarizacionCompra[i].NombreRazonSocialProveedor}|{listaBancarizacionCompra[i].NombreRazonSocialProveedor}|{listaBancarizacionCompra[i].NumeroFactura}|{listaBancarizacionCompra[i].NumeroContrato}|{listaBancarizacionCompra[i].ImporteFacturaDocumento.ToString("c").Replace(".", "").Replace(",", ".")}|{listaBancarizacionCompra[i].NumAutorizacionFacturaDocumento}|{listaBancarizacionCompra[i].NumCuentaDocumentoPago}|{listaBancarizacionCompra[i].MontoPagadoDocumentoPago.ToString("c").Replace(".", "").Replace(",", ".")}|{listaBancarizacionCompra[i].MontoAcumulado.ToString("c").Replace(".", "").Replace(",", ".")}|{listaBancarizacionCompra[i].NitEntidadFinanciera}|{listaBancarizacionCompra[i].NumDocumentoPago}|{(int)listaBancarizacionCompra[i].TipoDocumentoPago}|{listaBancarizacionCompra[i].FechaDocumentoPago.ToShortDateString()}";
}
File.WriteAllLines(saveFileDialog.FileName, lines);
Mensajes.CrearMensajeInformativo("Exportación exitosa");
if (Mensajes.CrearMensajeInterrogacion("¿Desea abrir el archivo?"))
{
Process.Start(saveFileDialog.FileName);
}
}
With the expectation that you are using dotnet 3.1 or higher you can use:
Option 1
Using just the File class.
// System.Text.Json serializer
await File.WriteAllTextAsync("Login.txt", JsonSerializer.Serialize(ListOfUsers));
Option 2
The other option without File is to use a StreamWriter instead.
using (var sw = new StreamWriter("Login.txt", true))
{
// System.Text.Json serializer
await sw.WriteLineAsync(JsonSerializer.Serialize(ListOfUsers));
}
Both will at the end produce the "Login.txt" file with a JSON format of the users list.
In case you are using a lower version than dotnet 3.1 you need to include Newtonsoft.Json which provides another JSON serializer:
// Newtonsoft serializer
JsonConvert.SerializeObject(ListOfUsers);
Important: Be aware (like written in the comments and other answers) this data is not encrypted and should not be written in that way.

Inserting json documents in DocumentDB

In DocumentDB documentation examples, I find insertion of C# objects.
// Create the Andersen family document.
Family AndersenFamily = new Family
{
Id = "AndersenFamily",
LastName = "Andersen",
Parents = new Parent[] {
new Parent { FirstName = "Thomas" },
new Parent { FirstName = "Mary Kay"}
},
IsRegistered = true
};
await client.CreateDocumentAsync(documentCollection.DocumentsLink, AndersenFamily);
In my case, I'm receiving json strings from application client and would like to insert them in DocumentDB without deserializing them. Could not find any examples of doing something similar.
Any help is sincerely appreciated..
Thanks
Copied from the published .NET Sample code -
private static async Task UseStreams(string colSelfLink)
{
var dir = new DirectoryInfo(#".\Data");
var files = dir.EnumerateFiles("*.json");
foreach (var file in files)
{
using (var fileStream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read))
{
Document doc = await client.CreateDocumentAsync(colSelfLink, Resource.LoadFrom<Document>(fileStream));
Console.WriteLine("Created Document: ", doc);
}
}
//Read one the documents created above directly in to a Json string
Document readDoc = client.CreateDocumentQuery(colSelfLink).Where(d => d.Id == "JSON1").AsEnumerable().First();
string content = JsonConvert.SerializeObject(readDoc);
//Update a document with some Json text,
//Here we're replacing a previously created document with some new text and even introudcing a new Property, Status=Cancelled
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes("{\"id\": \"JSON1\",\"PurchaseOrderNumber\": \"PO18009186470\",\"Status\": \"Cancelled\"}")))
{
await client.ReplaceDocumentAsync(readDoc.SelfLink, Resource.LoadFrom<Document>(memoryStream));
}
}

How to append node to existing json file using json.net

i am working with json in asp.net using json.NET where on button click values from textbox gets added to json file called country.json. There are two textbox which takes country and its capital as values,
country.json file looks like this,
[
{
"country":"USA",
"capital":"New York"
},
{
"country":"China",
"capital":"Bejing"
},
{
"country":"India",
"capital":"New Delhi"
}
]
i was able to create json with one node but how to append or add second node to existing json.
Here is the c# code ,
public class country
{
public string Country { get; set; }
public string Capital { get; set; }
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
country ctry = new country();
ctry.Country = txtCtry.Text;
ctry.Capital = txtCapital.Text;
File.AppendAllText(MapPath("Data/countrycaps.json"),JsonConvert.SerializeObject(ctry,Formatting.Indented));
}
If you want a list, you should be saving a list, not a single node.
Here are the steps:
If file exists, load all nodes from existing file into list.
Add new node when user provides data.
Save list to file.
I needed the same feature and the round-trip was just too expensive.
This is what I came up with:
private static void AppendTransaction(Transaction transaction)
{
const string filename = "transactions.json";
bool firstTransaction = !File.Exists(filename);
JsonSerializer ser = new JsonSerializer();
ser.Formatting = Formatting.Indented;
ser.TypeNameHandling = TypeNameHandling.Auto;
using (var fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read))
{
Encoding enc = firstTransaction ? new UTF8Encoding(true) : new UTF8Encoding(false);
using (var sw = new StreamWriter(fs, enc))
using (var jtw = new JsonTextWriter(sw))
{
if (firstTransaction)
{
sw.Write("[");
sw.Flush();
}
else
{
fs.Seek(-Encoding.UTF8.GetByteCount("]"), SeekOrigin.End);
sw.Write(",");
sw.Flush();
}
ser.Serialize(jtw, transaction);
sw.Write(']');
}
}
}

Categories