'C# JSON deserialize: failing stating I need an array when everything is already an array

I must be blind. This is what I am receiving from API:

{
    "temperatures": [
        {
            "field": "temp1",
            "values": [
                {
                    "value": 60.5,
                    "time": "2022-05-19T09:28:01.732662315Z"
                }
            ]
        },
        {
            "field": "temp2",
            "values": [
                {
                    "value": 33.8,
                    "time": "2022-05-19T09:28:01.732662315Z"
                }
            ]
        },
        {
            "field": "temp3",
            "values": [
                {
                    "value": 27.6,
                    "time": "2022-05-19T09:28:01.732662315Z"
                }
            ]
        },
        {
            "field": "temp4",
            "values": [
                {
                    "value": 26.6,
                    "time": "2022-05-19T09:28:01.732662315Z"
                }
            ]
        }
    ]
}

With this being my object model:

public class Temperatures
    {
        public temperatures[] temperatures { get; set; }
    }

    public class temperatures
    {
        public string field { get; set; }
        public TempValues[] values { get; set; }
    }

    public class TempValues
    {
        public string value { get; set; }
        public string time { get; set; }
    }

But these lines fail:

var response = await client.GetAsync(url);
            string content = (response.Content.ReadAsStringAsync().Result);
 ObservableCollection<Temperatures> res = JsonConvert.DeserializeObject<ObservableCollection<Temperatures>>(content);

saying that:

{Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.ObjectModel.ObservableCollection`1[oktopus.Helpers.Types.Temperatures]' because the type requires a JSON array (e.g. [1…}

What am I missing here? I tried making the observarble collection be an array, but this fails with the same error. Also I use the observable collection everywhere else with this API and this works, so it must be an issue with my object model. Can you spot it?



Solution 1:[1]

You're trying to deserialize to a collection of Temperatures, which would only be valid if the JSON started with [ and ended with ]. Instead, your JSON represents a single Temperatures object, which then contains an array as the value of the temperatures property.

So you just want:

Temperatures res = JsonConvert.DeserializeObject<Temperatures>(content);

(There are various other things you may well want to change, such as using conventional .NET names in the code and specifying attributes to indicate how they should be represented in JSON, and using a numeric type in TempValue, but the above should solve the immediate problem.)

Solution 2:[2]

You can't return a reference to a locally allocated String because the string is dropped when the function returns. There's no way to finagle your way around that. A &str is simply a bad match for the type of data you want to return.

The most straightforward fix is to return an owned String.

fn my_func(input: &str) -> String {
    match input {
        "a" => "Alpha".to_string(),
        _ => format!("'{}'", "Quoted" ), 
    }
}

Another is to return a Cow<'_, str>, which can hold either a borrowed or owned string depending on which you have. It's a bit fussy, but it does avoids unnecessary allocations. I only recommend this if efficiency is of utmost important; otherwise, just return String.

fn my_func(input: &str) -> Cow<'_, str> {
    match input {
        "a" => "Alpha".into(),
        _ => format!("'{}'", "Quoted" ).into(), 
    }
}

I'll also mention a third option -- for educational purposes, not for actual use, since it leaks memory. You can get a 'static reference to an owned object if you leak it. Leaked memory is valid for the remainder of the program since it's never freed, and thus you can in fact get a reference to it.

// Warning: Do not use! Leaks memory.
fn my_func(input: &str) -> &'static str {
    match input {
        "a" => "Alpha",
        _ => Box::leak(format!("'{}'", "Quoted").into_boxed_str()), 
    }
}

Solution 3:[3]

The problem is that the arm with format!().as_str() produces an owned String, as soon as your function returns, the String is dropped and the &str reference would become invalid.

You can use std::borrow::Cow to allow a function to return both owned or borrowed strings.

Solution 4:[4]

In addition to the other answers, you can also change my_func to take a parameter that tells it where to put its result instead of returning it:

use std::fmt::Write;

fn my_func(output: &mut impl Write, input: &str) {
    match input {
        "a" => write!(output, "Apha").unwrap(), // in fact, a &str from a crate
        _ => write!(output, "'{}'", "Quoted" ).unwrap(), 
    };
}

fn main() {
    let mut s = String::new();
    my_func(&mut s, "a");
}

Playground

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jon Skeet
Solution 2
Solution 3 sebpuetz
Solution 4 Jmb