How to deserialize JSON data using System.Text.Json - c#

This time I am needing some help deserializing some data that comes from a Web API using using System.Text.Json. After searching all over the place I had found nothing that can really help me solving this issue
Here is a sample of the data:
For the UF Indicator:
{
"UFs": [
{
"Valor": "30.008,40",
"Fecha": "2021-09-10"
}
]
}
For USD Indicator:
{
"Dolares": [
{
"Valor": "791,28",
"Fecha": "2021-09-10"
}
]
}
For UTM Indicator:
{
"UTMs": [
{
"Valor": "52.631",
"Fecha": "2021-09-01"
}
]
}
And a sample for USD with multiple sets of data:
{
"Dolares": [
{
"Valor": "767,10",
"Fecha": "2021-09-02"
},
{
"Valor": "768,36",
"Fecha": "2021-09-03"
},
{
"Valor": "766,53",
"Fecha": "2021-09-06"
},
{
"Valor": "770,33",
"Fecha": "2021-09-07"
},
{
"Valor": "777,94",
"Fecha": "2021-09-08"
},
{
"Valor": "787,51",
"Fecha": "2021-09-09"
},
{
"Valor": "791,28",
"Fecha": "2021-09-10"
}
]
}
This is the class I need to Deserialize to:
public class Indicador : IIndicador
{
public string Valor { get; set; }
public string Fecha { get; set; }
}
The issue starts when I try to Deserialize using this:
var dataFromApi = await httpResponse.Content.ReadAsStringAsync();
var indicador = JsonSerializer.Deserialize<Indicador>(dataFromApi);
I also tried using this "solution" but with no luck at all:
var dataFromApi = await httpResponse.Content.ReadAsStringAsync();
var indicador = JsonSerializer.Deserialize<Dictionary<string,List<indicador>>>(dataFromApi);
So far the only solution has been to create a "container" class that can help me handle that odd "UFs" as a list, since this WebAPI can return multiple other economics indicators than "UFs" which can change to a lot of other concepts, is there a way to map what ever comes from the WebAPI to my generic Indicador class?, the idea is to use a simple object when there is just 1 data and an IEnumerable when there are more than one. I need to stop the dependency for each type of indicator available.

Concerns / Areas of Improvement
Your final paragraph is a little vague and you don't have anymore examples. As such the answer might not be quite as specific as you are looking for.
multiple other economics indicators than "UFs"
Would be nice to see some other examples.
map what ever comes from the WebAPI to my generic Indicador class
Potentially possible, but it remains to be seen how the other data is structured.
Simplest Example
I think you are missing this part of the deserialization process
List<Indicador> indicadors = dataDict["UFs"]; // <-- Will fail If "UFs" is not present
Below is a top level C# program to how to deserialize the given data to a List<Indicador>.
using System;
using System.Text.Json;
using System.Collections.Generic;
// test data
string dataFromApi = #"{
""UFs"": [
{
""Valor"": ""30.008,40"",
""Fecha"": ""2021-09-10""
},
{
""Valor"": ""40.008,50"",
""Fecha"": ""2021-10-10""
}
]
}";
var dataDict = JsonSerializer.Deserialize<Dictionary<string, List<Indicador>>>(dataFromApi);
List<Indicador> indicadors = dataDict["UFs"]; // <-- Will fail If "UFs" is not present
// print out the indicadors
indicadors.ForEach(indicador => Console.WriteLine(indicador));
// Using records because they are brief and come with a good default ToString() method
// You can use regular class if you require
public abstract record IIndicador(string Valor, string Fecha);
public record Indicador(string Valor, string Fecha): IIndicador(Valor, Fecha);
The output of the above top level C# program
Indicador { Valor = 30.008,40, Fecha = 2021-09-10 }
Indicador { Valor = 40.008,50, Fecha = 2021-10-10 }
If "UFs" key is not guaranteed then you can use one of the Dictionary methods to determine if the key is present. For example
if (dataDict.ContainsKey("UFs")) ...
Speculation
Going out on a limb here trying to address some of the aspects of your last paragraph. (You will need to clarify if this address all your concerns and adapt to meet your needs) System.Text.Json also has JsonConverterFactory and JsonConverter<T> for more advanced Conversion requirements should you need them.
using System;
using System.Linq;
using System.Text.Json;
using System.Collections.Generic;
// test data
string multiDataFromApi = #"{
""UFs"": [
{
""Valor"": ""30.008,40"",
""Fecha"": ""2021-09-10""
},
{
""Valor"": ""40.008,50"",
""Fecha"": ""2021-10-10""
}
],
""UFOs"": [
{
""Valor"": ""30.008,40"",
""Fecha"": ""2021-09-10""
}
]
}";
string singleDataFromApi = #"{
""UFs"": [
{
""Valor"": ""30.008,40"",
""Fecha"": ""2021-09-10""
},
{
""Valor"": ""40.008,50"",
""Fecha"": ""2021-10-10""
}
]
}";
processDataFromApi(multiDataFromApi);
processDataFromApi(singleDataFromApi);
void processDataFromApi(string json)
{
var dataDict = JsonSerializer.Deserialize<Dictionary<string, List<Indicador>>>(json);
if (dataDict.Count == 1)
{
Console.WriteLine("-- Single Key Processing --");
List<Indicador> indicadors = dataDict.Values.First();
indicadors.ForEach(indicador => Console.WriteLine("\t{0}", indicador));
}
else
{
Console.WriteLine("-- Multi Key Processing --");
foreach (var keyValuePair in dataDict)
{
Console.WriteLine($"Processing Key: {keyValuePair.Key}");
List<Indicador> indicadors = keyValuePair.Value;
indicadors.ForEach(indicador => Console.WriteLine("\t{0}",indicador));
}
}
Console.WriteLine("-----------------------------");
}
// Using records because they are brief and come with a good default ToString() method
// You can use regular class if you require
public abstract record IIndicador(string Valor, string Fecha);
public record Indicador(string Valor, string Fecha): IIndicador(Valor, Fecha);
which will produce the following output
-- Multi Key Processing --
Processing Key: UFs
Indicador { Valor = 30.008,40, Fecha = 2021-09-10 }
Indicador { Valor = 40.008,50, Fecha = 2021-10-10 }
Processing Key: UFOs
Indicador { Valor = 30.008,40, Fecha = 2021-09-10 }
-----------------------------
-- Single Key Processing --
Indicador { Valor = 30.008,40, Fecha = 2021-09-10 }
Indicador { Valor = 40.008,50, Fecha = 2021-10-10 }
-----------------------------

Related

How to change the names of some elements of a JSON string in c#

I have a JSON string that I receive from a service call that has the following format:
{
"DataKEY": [
{
"value": {
"timestamp": "2022-10-26T05:00:00Z",
"value": 0.0
}
},
{
"value": {
"timestamp": "2022-10-26T06:00:00Z",
"value": 0.0
}
}
]
}
How can I change just the node names of the middle "value" to obtain the following output to send to another service:
{
"DataKEY": [
{
"KEY": {
"timestamp": "2022-10-26T05:00:00Z",
"value": 0.0
}
},
{
"KEY": {
"timestamp": "2022-10-26T06:00:00Z",
"value": 0.0
}
}
]
}
I need to change the first level of nodes with the name "value" to a given "KEY" in the response string, I tried using string.Replace("value", "KEY") but that replaces the inner level as well.
How can I change only the first level of nodes?
When the string represents a JSON data response then you will find it easier to manipulate the names of the properties or the structure of the object graph by first deserializing the data transforming the data to the desired output, then serialising the transformed object back into a string.
If we only need to change the names of some elements, you could use a JSON reader and writer process to step through the file or we can take advantage of some serialization tricks to avoid the transformation step.
We can configure the serialization provider to omit null valued properties which means we can provide 2 properties on a class definition that will be mapped to the same internal backing field, in the example below I have mapped a second property to the inner Key value, this only has a setter making it write-only. This way any deserialization process can write to it via Key or Value named property, but in the serialization process the Value property will always return null and can be omitted from the output:
this is the important aspect and probably the only scenario I've ever used a write-only property:
public DataKeyValue Key { get;set; }
public DataKeyValue Value
{
get
{
return null;
}
set
{
Key = value;
}
}
This is the full class definition:
public class DataKeyWrapper
{
public DataKey[] DataKEY { get;set; }
}
public class DataKey
{
public DataKeyValue Key { get;set; }
public DataKeyValue Value
{
get
{
return null;
}
set
{
Key = value;
}
}
}
public class DataKeyValue
{
public DateTime timestamp { get;set; }
public Decimal value { get;set; }
}
The you can deserialize and re-serialize with this logic:
using Newtonsoft.Json;
...
var input = JsonConvert.DeserializeObject<DataKeyWrapper>(input);
var output = JsonConvert.SerializeObject(input, new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
});
You can view a full fiddle on this here: https://dotnetfiddle.net/YUsRn8
The Response:
{
"DataKEY": [
{
"Key": {
"timestamp": "2022-10-26T05:00:00Z",
"value": 0.0
}
},
{
"Key": {
"timestamp": "2022-10-26T06:00:00Z",
"value": 0.0
}
}
]
}

How do I get info from an object with an array of objects in a json for Unity?

I have a JSON file setup as such
{
"cards": [
{
"id": "sand_bags",
"type": "Structure",
"name": "Sand Bags",
"values": [
{
"civilian": 1,
"fiend": -1
}
],
"effectTexts": [
{
"civilian": "Add +1 to your BUNKER.",
"fiend": "Send any CIVILIAN'S +1 STRUCTURE to the SCRAPYARD."
}
],
"flavorTexts": [
{
"civilian": "They're just heavy bags with sand. Not much else to say, but they'll slow down an attack from a fiend. Good luck, you'll need it!",
"fiend": "You've spotted a pretty bad STRUCTURE in this BUNKER. Time to do some damage."
}
],
"staysOnField": [
{
"civilian": true,
"fiend": false
}
],
"amountInDeck": 5
}
]
}
I also have a Cards script
[Serializable]
public class Cards
{
public Card[] cards;
}
[Serializable]
public class Card
{
public string id;
public string type;
public string name;
public int amountInDeck;
}
public class Values
{
public int civilian;
public int fiend;
}
I then have a CardEffects script that I'm using for my functions.
public class CardEffects : MonoBehaviour
{
public TextAsset jsonFile;
public Values values;
void Start()
{
Cards cardsInJson = JsonUtility.FromJson<Cards>(jsonFile.text);
foreach (Card card in cardsInJson.cards)
{
Debug.Log("Card name: " + card.name + " with " + values.civilian + " in the deck.");
}
}
}
I have searched all over trying to figure out how to even get the array of objects of "values". I got this far and the value printed is always 0 regardless of the information in "values" in the JSON. If I make the class Serializable, I'm able to change the values and it works but I want the values to be whatever they were declared as in the JSON. Is there a better way to do this?
Please keep in mind I'm new to C# and Unity. I usually code in JS in which using JSON files are no big deal for me and thought it was the best way to go.
Your json and your classes doesn't match. Not only that your json isn't even valid Json. Below I will give you your correct json and class.
{
"cards": [
{
"id": "sand_bags",
"type": "Structure",
"name": "Sand Bags",
"values": [
{
"civilian": 1,
"fiend": -1
}
],
"effectTexts": [
{
"civilian": "Add +1 to your BUNKER.",
"fiend": "Send any CIVILIAN'S +1 STRUCTURE to the SCRAPYARD."
}
],
"flavorTexts": [
{
"civilian": "They're just heavy bags with sand. Not much else to say, but they'll slow down an attack from a fiend. Good luck, you'll need it!",
"fiend": "You've spotted a pretty bad STRUCTURE in this BUNKER. Time to do some damage."
}
],
"staysOnField": [
{
"civilian": true,
"fiend": false
}
],
"amountInDeck": 5
}
] // <---- This was missing
}
For that Json this is how your card class will have to look like:
public Card
{
public string id { get; set; }
public string type { get; set; }
public string name { get; set; }
public Values[] values { get; set; }
public Values[] effectTexts { get; set; }
public Values[] staysOnField { get; set; }
public int amountInDeck { get; set; }
}
And your Values class have to look like this:
public class Values
{
public object civilian;
public object fiend;
}
I suggest MiniJson.
You can just copy paste the script to your project and you are ready to go.
The then can call Json.Deserialize(string json) passing your string in. For the case of an array you can do for example:
IList myJsonElements = (IList)Json.Deserialize(string json);
Find working snippet:
using System;
using Newtonsoft.Json;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args) {
string myJson = "{\"cards\": [{\"id\": \"sand_bags\",\"type\": \"Structure\",\"name\": \"Sand Bags\",\"values\": [{\"civilian\": 1,\"fiend\": -1}],\"effectTexts\": [{\"civilian\": \"Add +1 to your BUNKER.\",\"fiend\": \"Send any CIVILIAN'S +1 STRUCTURE to the SCRAPYARD.\"}],\"flavorTexts\": [{\"civilian\": \"They're just heavy bags with sand. Not much else to say, but they'll slow down an attack from a fiend. Good luck, you'll need it!\",\"fiend\":\"You've spotted a pretty bad STRUCTURE in this BUNKER. Time to do some damage.\"}],\"staysOnField\": [{\"civilian\": true,\"fiend\": false}],\"amountInDeck\": 5}]}";
var myJsonElements = MiniJSON.Json.Deserialize(myJson);
Console.WriteLine(myJsonElements.ToString());
string json = JsonConvert.SerializeObject(myJsonElements, Formatting.Indented);
Console.WriteLine(json);
Console.ReadLine();
}
}
}

Create Excel with JSON

I have an API who takes any JSON and I want to make a Excel with it. I'm using
Json.Decode(json)
for convert that JSON in a dynamic object, but I don't know how to access to any key and value created by the decoder.
How can I reference each key and value created?
My code:
Request model
/// <summary>
/// Request de servicio Excel
/// </summary>
public class GenerarExcelRequest
{
/// <summary>
/// Lista de elementos
/// </summary>
public string NombreArchivo { get; set; }
/// <summary>
/// JSON a convertir en Excel
/// </summary>
public object Modelo { get; set; }
}
Service
public GenerarExcelResponse GenerarExcel(GenerarExcelRequest request)
{
using (ExcelPackage exc = new ExcelPackage())
{
ExcelWorksheet ex = exc.Workbook.Worksheets.Add("Reporte Excel");
// If the dynamic object != null, convert it
var modeloDecode = request.Modelo != null ? request.Modelo = Json.Decode(request.Modelo.ToString()) : null;
// I get every value and key created and make the key header of the Excel
if (modeloDecode == null)
return new GenerarExcelResponse() { RutaArchivoExcel = ""};
//var listaEncabezados =
// Load every value in the Excel
// Return the file path of the new Excel
string filePath = "C:\\" + request.NombreArchivo;
byte[] bin = exc.GetAsByteArray();
File.WriteAllBytes(filePath, bin);
return new GenerarExcelResponse()
{
RutaArchivoExcel = filePath
};
}
}
JSON Input Example:
{
"NombreArchivo": "Prueba",
"Modelo": [
{
"id": 24135,
"nombre": "Ignacio"
},
{
"id": 28733,
"nombre": "Francisco"
}
]
}
Excel Output I want
Id ---------- Nombre
24135 ------- Ignacio
28733 ------- Francisco
But the next time that someone use this API may send this input:
JSON Input Example 2:
{
"NombreArchivo": "Prueba2",
"Modelo": [
{
"id": 25,
"product": "XXAA2121",
"stock": 21
},
{
"id": 23,
"product": "XXFJJ212"
"stock": 4
}
]
}
And want to make and Excel like this:
Excel Output I want
Id ---------- Product --------- Stock
25 ---------- XXAA2121 -------- 21
23 ---------- XXFJJ212 -------- 4
I'm not exactly sure what you want, if this should work regardless of the structure of the doc, but something like this might do what you need to get it into a flat list. From there, its a trivial matter to put it into excel. This is a working example from Linqpad once you add Json.NET as a nuget package.
void Main()
{
var jsonFoo = #"{
""NombreArchivo"": ""Prueba"",
""Modelo"": [
{
""id"": 24135,
""nombre"": ""Ignacio""
},
{
""id"": 28733,
""nombre"": ""Francisco""
}
]
}";
var foo = (JObject)JsonConvert.DeserializeObject(jsonFoo);
foo.Dump();
var dict = new List<Tuple<string,string>>();
ConvertJsonToDictionary(foo, dict);
dict.Dump();
}
// Define other methods and classes here
public void ConvertJsonToDictionary(JToken foo, List<Tuple<string,string>> dict)
{
switch(foo.Type)
{
case JTokenType.Object:
foreach (var item in (JObject)foo)
{
dict.Add(new Tuple<string,string>(item.Key, item.Value.ToString()));
if (item.Value.GetType() == typeof(JArray))
{
ConvertJsonToDictionary(item.Value, dict);
}
}
break;
case JTokenType.Array:
foreach(var item in (JArray)foo)
{
ConvertJsonToDictionary(item, dict);
}
break;
}
}

Using Facets in the Aggregation Framework C#

I would like to create an Aggregation on my data to get the total amount of counts for specific tags for a collection of books in my .Net application.
I have the following Book class.
public class Book
{
public string Id { get; set; }
public string Name { get; set; }
[BsonDictionaryOptions(DictionaryRepresentation.Document)]
public Dictionary<string, string> Tags { get; set; }
}
And when the data is saved, it is stored in the following format in MongoDB.
{
"_id" : ObjectId("574325a36fdc967af03766dc"),
"Name" : "My First Book",
"Tags" : {
"Edition" : "First",
"Type" : "HardBack",
"Published" : "2017",
}
}
I've been using facets directly in MongoDB and I am able to get the results that I need by using the following query:
db.{myCollection}.aggregate(
[
{
$match: {
"Name" : "SearchValue"
}
},
{
$facet: {
"categorizedByTags" : [
{
$project :
{
Tags: { $objectToArray: "$Tags" }
}
},
{ $unwind : "$Tags"},
{ $sortByCount : "$Tags"}
]
}
},
]
);
However I am unable to transfer this over to the .NET C# Driver for Mongo. How can I do this using the .NET C# driver?
Edit - I will ultimately be looking to query the DB on other properties of the books as part of a faceted book listings page, such as Publisher, Author, Page count etc... hence the usage of $facet, unless there is a better way of doing this?
I would personally not use $facet here since you've only got one pipeline which kind of defeats the purpose of $facet in the first place...
The following is simpler and scales better ($facet will create one single potentially massive document).
db.collection.aggregate([
{
$match: {
"Name" : "My First Book"
}
}, {
$project: {
"Tags": {
$objectToArray: "$Tags"
}
}
}, {
$unwind: "$Tags"
}, {
$sortByCount: "$Tags"
}, {
$group: { // not really needed unless you need to have all results in one single document
"_id": null,
"categorizedByTags": {
$push: "$$ROOT"
}
}
}, {
$project: { // not really needed, either: remove _id field
"_id": 0
}
}])
This could be written using the C# driver as follows:
var collection = new MongoClient().GetDatabase("test").GetCollection<Book>("test");
var pipeline = collection.Aggregate()
.Match(b => b.Name == "My First Book")
.Project("{Tags: { $objectToArray: \"$Tags\" }}")
.Unwind("Tags")
.SortByCount<BsonDocument>("$Tags");
var output = pipeline.ToList().ToJson(new JsonWriterSettings {Indent = true});
Console.WriteLine(output);
Here's the version using a facet:
var collection = new MongoClient().GetDatabase("test").GetCollection<Book>("test");
var project = PipelineStageDefinitionBuilder.Project<Book, BsonDocument>("{Tags: { $objectToArray: \"$Tags\" }}");
var unwind = PipelineStageDefinitionBuilder.Unwind<BsonDocument, BsonDocument>("Tags");
var sortByCount = PipelineStageDefinitionBuilder.SortByCount<BsonDocument, BsonDocument>("$Tags");
var pipeline = PipelineDefinition<Book, AggregateSortByCountResult<BsonDocument>>.Create(new IPipelineStageDefinition[] { project, unwind, sortByCount });
// string based alternative version
//var pipeline = PipelineDefinition<Book, BsonDocument>.Create(
// "{ $project :{ Tags: { $objectToArray: \"$Tags\" } } }",
// "{ $unwind : \"$Tags\" }",
// "{ $sortByCount : \"$Tags\" }");
var facetPipeline = AggregateFacet.Create("categorizedByTags", pipeline);
var aggregation = collection.Aggregate().Match(b => b.Name == "My First Book").Facet(facetPipeline);
var output = aggregation.Single().Facets.ToJson(new JsonWriterSettings { Indent = true });
Console.WriteLine(output);

How to create set of Multiple JSON with same header

I have a JSON that has the following pattern to be created before
hitting the API, See below
"recipientSetInfos":
[
{
"recipientSetMemberInfos":
[
{
"fax": "",
"email": ""
}
],
"recipientSetRole":
{
"SIGNER": "enum",
"APPROVER": "enum"
},
"signingOrder": 0
}
]
Using this predefined Format, i want to create Multiple signer set's,
like the below.
"recipientSetInfos":
[
{
"recipientSetMemberInfos":
[{
"email": "def#gmail.com"
}],
"recipientSetRole": "SIGNER"
}, {
"recipientSetMemberInfos": [{
"email": "abc#gmail.com"
}],
"recipientSetRole": "SIGNER"
}],
I am using C# Programming Language, if i just hard code it & send. It
works but if i want to create dynamically. How can i achieve this.
Currently I am using this like
RecipientSetInfo rec_Info = new RecipientSetInfo();
rec_Info.recipientSetMemberInfos = List_Emails;
rec_Info.recipientSetRole = "SIGNER";
List_Recipients.Add(rec_Info);
which gives an output of :
{
"recipientSetMemberInfos":
[
{"email":"abc#ae.com"},
{"email":"def#gmail.com"},
{"email":"fgh#gmail.com"}
],
"recipientSetRole":"SIGNER"
}
But using this logic, i am not getting the desired output. It is
considering all 3 emails as one.
Just to add, one more thing with the help of one user, i tried to code out this
foreach (var email in List_Emails)
{
var rec_Info = new RecipientSetInfo();
rec_Info.recipientSetRole = "SIGNER";
List_Recipients.Add(rec_Info);
}
But problem still exists, since "recipientSetInfos" has two subdivisions i.e. recipientSetRole & recipientSetMemberInfos within which "recipientSetMemberInfos" has one attribute Email.
So when i add the two lists together it comes out Email to be Null
"recipientSetInfos":
[
{"recipientSetMemberInfos":null,
"recipientSetRole":"SIGNER"
‌​},
{"recipientSetMemberInfos":null,
"recipientSetRole":"SIGNER"
}
]
Structure for both the elements i have created like -
public class RecipientSetMemberInfo
{
public string email { get; set; }
}
public class RecipientSetInfo
{
public List<RecipientSetMemberInfo> recipientSetMemberInfos { get; set; }
public string recipientSetRole { get; set; }
}
Please suggest ??
Your problem is that you create one RecipientSetInfo instead of one for each email.
The following will loop through List_Emails collection and will add them to the list List_Recipients.
foreach (var email in List_Emails)
{
var rec_Info = new RecipientSetInfo();
rec_Info.recipientSetRole = "SIGNER";
List_Recipients.Add(rec_Info);
}

Categories

Resources