AcidRod75
AcidRod75

Reputation: 187

How to deserialize JSON data using System.Text.Json

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.

Upvotes: 1

Views: 1334

Answers (1)

Kwiksilver
Kwiksilver

Reputation: 855

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 }
-----------------------------

Upvotes: 3

Related Questions