Your Note class needs a parameterless constructor

class Note
{
    public DateTime currentDate { get; set; }
    public string summary { get; set; }

    // add this
    public Note()
    {
    }

    public Note(DateTime _date, string _sum)
    {
        currentDate = _date;
        summary = _sum;
    }
}

It might be worth thinking if you need your original two parameter constructor. If you removed it, then you could instantiate a new Note like this

var completeNote = new Note
{
    currentdate = date,
    summary = givenNote
};
Answer from Kevin Brydon on Stack Overflow
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › standard › serialization › system-text-json › deserialization
How to deserialize JSON in C# - .NET | Microsoft Learn
For example, you can customize the prompt to demonstrate deserialization when the target class defines a property that's missing from the JSON input. The following text shows an example prompt for Copilot Chat: Generate C# code to use System.Text.Json to deserialize a JSON string {"FirstName":"John","LastName":"Doe"} to an equivalent .NET object, where the class defines an Age property.
Discussions

System.Text.Json failure to deserialize strongly typed models from string
System.Text.Json fails to deserialize all properties under all circumstances for all models. It cannot deserialize anything into an object from string, stream, or otherwise. Newtonsoft works as expected -> reads json string and produces a list of objects set by the json properties. System does not work, it always returns null/default/0/Empty values. This is true ... More on github.com
🌐 github.com
8
July 1, 2022
System.Text.Json fails to deserialize into a model with an ImmutableList property.
Description The StackOverflow issue https://stackoverflow.com/questions/72268018/jsonconstructor-fails-on-ienumerable-property describes a scenario trying to deserialize an object that has an immutable collection as a property using the ... More on github.com
🌐 github.com
3
May 17, 2022
c# - System.Text.Json.deserialize Json String - Stack Overflow
Find centralized, trusted content and collaborate around the technologies you use most. Learn more about Collectives ... Bring the best of human thought and AI automation together at your work. Explore Stack Internal ... I have a question about System.Text.Json.deserialize. More on stackoverflow.com
🌐 stackoverflow.com
Check if json string can be deserialized into specific class
You can add required keyword to the Name property, after which the JSON serializer expects that property to be present. More on reddit.com
🌐 r/dotnet
4
11
February 19, 2024
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › standard › serialization › system-text-json › how-to
How to serialize JSON in C# - .NET | Microsoft Learn
To pretty-print the JSON output, set JsonSerializerOptions.WriteIndented to true: using System.Text.Json; namespace SerializeWriteIndented { public class WeatherForecast { public DateTimeOffset Date { get; set; } public int TemperatureCelsius { get; set; } public string?
🌐
GitHub
github.com › dotnet › aspnetcore › issues › 42545
System.Text.Json failure to deserialize strongly typed models from string · Issue #42545 · dotnet/aspnetcore
July 1, 2022 - System.Text.Json fails to deserialize all properties under all circumstances for all models. It cannot deserialize anything into an object from string, stream, or otherwise. Newtonsoft works as expected -> reads json string and produces a list ...
Author   optimizasean
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › api › system.text.json.jsonserializer.deserialize
JsonSerializer.Deserialize Method (System.Text.Json) | Microsoft Learn
The Stream will be read to completion. public: static System::Object ^ Deserialize(System::IO::Stream ^ utf8Json, System::Text::Json::Serialization::Metadata::JsonTypeInfo ^ jsonTypeInfo);
🌐
GitHub
github.com › dotnet › runtime › issues › 69417
System.Text.Json fails to deserialize into a model with an ImmutableList property. · Issue #69417 · dotnet/runtime
May 17, 2022 - System.Text.Json fails to deserialize into a model with an ImmutableList property.#69417 · Copy link · Labels · area-System.Text.Json · ericrini · opened · on May 17, 2022 · Issue body actions · The StackOverflow issue https://stackoverflow.com/questions/72268018/jsonconstructor-fails-on-ienumerable-property describes a scenario trying to deserialize an object that has an immutable collection as a property using the System.Text.Json serializer.
Published   May 17, 2022
Author   ericrini
Top answer
1 of 3
3

Don't know why you want to use the JsonNode.Parse().

If you just want to modify the location's value, maybe you can use the JsonSerializer with BookStore and Book class to help you.
The Book class is a IEnumerable property of BookStore.

an example by .NET6 Console App
BookClass is a 2-level nested class

// See https://aka.ms/new-console-template for more information
using System.Text.Json;

// Initialize json string
string jsonStr = "{\"id\": 1, \"ShopName\" :\"Paint Shop\", \"Books\" : [\r\n            {\r\n                \"bookId\":1,\r\n                \"bookName\":\"Peter Pan\",\r\n                \"location\":\"A01\"\r\n            },\r\n            {\r\n                \"bookId\":2,\r\n                \"bookName\":\"Cooking Book\",\r\n                \"location\":\"A02\"\r\n            }\r\n        ]\r\n    }";

// Deserilize to object
Bookstore bookstore = JsonSerializer.Deserialize<Bookstore>(jsonStr);

// Write the location's value
Console.WriteLine(bookstore.Books.ToArray()[1].location);

// Modified the location's value
bookstore.Books.ToArray()[1].location += "_Modified";

// Write the modified location's value
Console.WriteLine(bookstore.Books.ToArray()[1].location);

// See result output
Console.ReadLine();

public class Bookstore
{
    public int id { get; set; }
    public string ShopName { get; set; }
    public IEnumerable<Book> Books { get; set; }
}

public class Book
{
    public int bookId { get; set; }
    public string bookName { get; set; }
    public string location { get; set; }
}

Result output image


If you persist to use JsonNode.Parse(), then need more steps. like this.

// See https://aka.ms/new-console-template for more information
using System.Text.Json;
using System.Text.Json.Nodes;

// Initialize json string
string jsonStr = "{\"id\": 1, \"ShopName\" :\"Paint Shop\", \"Books\" : [\r\n            {\r\n                \"bookId\":1,\r\n                \"bookName\":\"Peter Pan\",\r\n                \"location\":\"A01\"\r\n            },\r\n            {\r\n                \"bookId\":2,\r\n                \"bookName\":\"Cooking Book\",\r\n                \"location\":\"A02\"\r\n            }\r\n        ]\r\n    }";

// Parse to jsonNode
JsonNode jsonNode = JsonNode.Parse(jsonStr);

// Convert to JsonObject
JsonObject jsonObject = jsonNode.AsObject();

// Convert Books to Json Array
JsonArray jsonArray = jsonObject["Books"].AsArray();

// Convert index 1 in array to Json object
JsonObject book1Object = jsonArray[1].AsObject();

// Write the location value 
Console.WriteLine(book1Object["location"]);

// Modify location value 
book1Object["location"] += "_modified";

// Write the modified location value 
Console.WriteLine(book1Object["location"]);

// See output
Console.ReadLine();

public class Bookstore
{
    public int id { get; set; }
    public string ShopName { get; set; }
    public IEnumerable<Book> Books { get; set; }
}

public class Book
{
    public int bookId { get; set; }
    public string bookName { get; set; }
    public string location { get; set; }
}


Other suggestion

Use Newtonsoft.Json is better. Because when convert a json to a C# object, will need to convert many properties with different value type. The Newtonsoft.Json had already handle these situation, at the mean time the System.Text.Json does not.

2 of 3
1

You can use JObject to modify json directly

var jObject = JObject.Parse(json);

jObject["Books"][1]["location"] = "new value";

Result:

Note:

You should add Newtonsoft.Json package.

Find elsewhere
🌐
Josef Ottosson
josef.codes › protect-yourself-when-deserializing-system-text-json
Protect yourself when deserializing - System.Text.Json
February 27, 2024 - I've shown two approaches to protect yourselve against "bad" json. It's better to get an exception when trying to deserialize the json compared to getting an obscure null reference exception later on in your code.
🌐
Marc Roussy
marcroussy.com › 2020 › 08 › 17 › deserialization-with-system-text-json
Deserialization with System.Text.Json – Marc Roussy
January 22, 2021 - System.Text.Json adheres to RFC 8259, so if you’re ever left wondering why a setting is different from Newtonsoft, that’s probably why. ... Have a POCO that matches the JSON data, or it’s easy to create one. Need to use most of the properties of the JSON in your application code. JsonDocument.Parse deserializes JSON to its constituent parts and makes it accessible through an object model that can represent any valid JSON.
🌐
Reddit
reddit.com › r/dotnet › check if json string can be deserialized into specific class
r/dotnet on Reddit: Check if json string can be deserialized into specific class
February 19, 2024 -

So I have a json string like:

"{\"thisisnorthecorrectname\":\"Value1\"}"

Now I want to use System.Text.Json to check if this string can be deserialized into this class:

internal class Test {
    public string Name { get; set; }
}

I have tried with a method like this:

static bool CanDeserialize<T>(string jsonString, out T deserializedObject) {
    deserializedObject = default;
    try {
        deserializedObject = JsonSerializer.Deserialize<T>(jsonString);
        return true;
    }
    catch (JsonException) {
        return false;
    }
}

But this does not throw an Exception. Instead it gives me a class object where the property name is NULL.

How can I check if the json string can be deserialized properly into a class?

Top answer
1 of 8
99

@Victor LG's answer using Newtonsoft is close, but it doesn't technically avoid the a catch as the original poster requested. It just moves it elsewhere. Also, though it creates a settings instance to enable catching missing members, those settings aren't passed to the DeserializeObject call so they are actually ignored.

Here's a "catch free" version of his extension method that also includes the missing members flag. The key to avoiding the catch is setting the Error property of the settings object to a lambda which then sets a flag to indicate failure and clears the error so it doesn't cause an exception.

 public static bool TryParseJson<T>(this string @this, out T result)
 {
    bool success = true;
    var settings = new JsonSerializerSettings
    {
        Error = (sender, args) => { success = false; args.ErrorContext.Handled = true; },
        MissingMemberHandling = MissingMemberHandling.Error
    };
    result = JsonConvert.DeserializeObject<T>(@this, settings);
    return success;
}

Here's an example to use it:

if(value.TryParseJson(out MyType result))
{ 
    // Do something with result…
}
2 of 8
64

With Json.NET you can validate your json against a schema:

 string schemaJson = @"{
 'status': {'type': 'string'},
 'error': {'type': 'string'},
 'code': {'type': 'string'}
}";

JsonSchema schema = JsonSchema.Parse(schemaJson);

JObject jobj = JObject.Parse(yourJsonHere);
if (jobj.IsValid(schema))
{
    // Do stuff
}

And then use that inside a TryParse method.

public static T TryParseJson<T>(this string json, string schema) where T : new()
{
    JsonSchema parsedSchema = JsonSchema.Parse(schema);
    JObject jObject = JObject.Parse(json);

    return jObject.IsValid(parsedSchema) ? 
        JsonConvert.DeserializeObject<T>(json) : default(T);
}

Then do:

var myType = myJsonString.TryParseJson<AwsomeType>(schema);

Update:

Please note that schema validation is no longer part of the main Newtonsoft.Json package, you'll need to add the Newtonsoft.Json.Schema package.

Update 2:

As noted in the comments, "JSONSchema" have a pricing model, meaning it isn't free. You can find all the information here

Top answer
1 of 2
10

I've had a look at your fiddle and spotted a couple of problems. Working fiddle here

  1. System.Text.Json is case-sensitive by default (except for web apps). You can resolve this by using either PropertyNamingPolicy = JsonNamingPolicy.CamelCase or PropertyNameCaseInsensitive = true in the serializer options.

  2. The second issue is outlined in Enums as strings

    By default, enums are serialized as numbers. To serialize enum names as strings, use the JsonStringEnumConverter.

    You should add JsonSerializerOptions to resolve (1) and (2):

    var options = new JsonSerializerOptions 
    {
        Converters =
        {
            new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
        },
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase
    };
    var items = JsonSerializer.Deserialize<List<MenuItem>>(json, options);
    
  3. The third issue appears to be with the binding in the constructor for the list of Permissions. In the constructor you define a List<Permission> for the permissions parameter. I receive the error in your question unless the constructor argument type matches the model property type exactly. So, I updated the constructor to take a IReadOnlyList<Permission> and it deserializes successfully:

    [JsonConstructor]
    public MenuItem
        (
            string name, 
            string url,
            IReadOnlyList<Permission> permissions
        )
    {
        this.Name = name;
        this.Url = url;
        this.Permissions = permissions ?? new List<Permission>().AsReadOnly();
    }
    

    Alternatively, you could change the Permissions property to List<Permission>.

This answer to a question with a similar problem explains that this is actually a limitation of System.Text.Json and there is currently an open github issue.

A working fork of your fiddle is demoed here.

2 of 2
0

I had the same issue. The solution is to use

JsonSerializable

attribute of

System.Text.Json.Serialization

It looks like:

[JsonSerializable(typeof(YourClassName))]
public class YourClassName
{
    public byte YourPropertyName { get; set; }
}

Always class and always public and always properties and always use the attribute on the class.

🌐
GitHub
github.com › dotnet › runtime › issues › 30969
[System.Text.Json] serialize/deserialize any object · Issue #30969 · dotnet/runtime
September 26, 2019 - JsonSerializerSettings ...SerializeObject(value, jsonSerializerSettings); // deserialize object value = JsonConvert.DeserializeObject(json, jsonSerializerSettings);...
Author   MichaelRumpler
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › standard › serialization › system-text-json › overview
Serialize and deserialize JSON using C# - .NET | Microsoft Learn
Access to this page requires authorization. You can try changing directories. ... The System.Text.Json namespace provides functionality for serializing to and deserializing from (or marshalling and unmarshalling) JavaScript Object Notation (JSON). Serialization is the process of converting ...
🌐
DEV Community
dev.to › prameshkc › handling-non-standard-json-in-net-using-systemtextjson-3e97
Handling Non-Standard JSON in .NET Using System.Text.Json - DEV Community
January 4, 2024 - If we try to deserialize the above JSON, we will get the following exception: System.Text.Json.JsonException: The JSON object contains a trailing comma at the end which is not supported in this mode.
Top answer
1 of 1
6

Your JSON root object consists of certain fixed keys ("sol_keys" and "validity_checks") whose values each have some fixed schema, and any number of variable keys (the "782" numeric keys) whose values all share a common schema that differs from the schemas of the fixed key values:

{
  "782": {
    // Properties corresponding to your MarsWheather object
  },
  "783": {
    // Properties corresponding to your MarsWheather object
  },
  // Other variable numeric key/value pairs corresponding to KeyValuePair<string, MarsWheather>
  "sol_keys": [
    // Some array values you don't care about
  ],
  "validity_checks": {
    // Some object you don't care about
  }
}

You would like to deserialize just the variable keys, but when you try to deserialize to a Dictionary<string, MarsWheather> you get an exception because the serializer tries to deserialize a fixed key value as if it were variable key value -- but since the fixed key has an array value while the variable keys have object values, an exception gets thrown. How can System.Text.Json be told to skip the known, fixed keys rather than trying to deserialize them?

If you want to deserialize just the variable keys and skip the fixed, known keys, you will need to create a custom JsonConverter. The easiest way to do that would be to first create some root object for your dictionary:

[JsonConverter(typeof(MarsWheatherRootObjectConverter))]
public class MarsWheatherRootObject
{
    public Dictionary<string, MarsWheather> MarsWheathers { get; } = new Dictionary<string, MarsWheather>();
}

And then define the following converter for it as follows:

public class MarsWheatherRootObjectConverter : FixedAndvariablePropertyNameObjectConverter<MarsWheatherRootObject, Dictionary<string, MarsWheather>, MarsWheather>
{
    static readonly Dictionary<string, ReadFixedKeyMethod> FixedKeyReadMethods = new Dictionary<string, ReadFixedKeyMethod>(StringComparer.OrdinalIgnoreCase)
    {
        { "sol_keys", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => reader.Skip() },
        { "validity_checks", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => reader.Skip() },
    };

    protected override Dictionary<string, MarsWheather> GetDictionary(MarsWheatherRootObject obj) => obj.MarsWheathers;
    protected override void SetDictionary(MarsWheatherRootObject obj, Dictionary<string, MarsWheather> dictionary) => throw new RowNotInTableException();
    protected override bool TryGetFixedKeyReadMethod(string name, JsonSerializerOptions options, out ReadFixedKeyMethod method) => FixedKeyReadMethods.TryGetValue(name, out method);
    protected override IEnumerable<KeyValuePair<string, WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options) => Enumerable.Empty<KeyValuePair<string, WriteFixedKeyMethod>>();
}

public abstract class FixedAndvariablePropertyNameObjectConverter<TObject, TDictionary, TValue> : JsonConverter<TObject> 
    where TDictionary : class, IDictionary<string, TValue>, new()
    where TObject : new()
{
    protected delegate void ReadFixedKeyMethod(ref Utf8JsonReader reader, TObject obj, string name, JsonSerializerOptions options);
    protected delegate void WriteFixedKeyMethod(Utf8JsonWriter writer, TObject value, JsonSerializerOptions options);
        
    protected abstract TDictionary GetDictionary(TObject obj);
    protected abstract void SetDictionary(TObject obj, TDictionary dictionary);
    protected abstract bool TryGetFixedKeyReadMethod(string name, JsonSerializerOptions options, out ReadFixedKeyMethod method);
    protected abstract IEnumerable<KeyValuePair<string, WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options);
        
    public override TObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Null)
            return (typeToConvert.IsValueType && Nullable.GetUnderlyingType(typeToConvert) == null)
                ? throw new JsonException(string.Format("Unepected token {0}", reader.TokenType))
                : default(TObject);
        if (reader.TokenType != JsonTokenType.StartObject)
            throw new JsonException(string.Format("Unepected token {0}", reader.TokenType));
        var obj = new TObject();
        var dictionary = GetDictionary(obj);
        var valueConverter = (typeof(TValue) == typeof(object) ? null : (JsonConverter<TValue>)options.GetConverter(typeof(TValue))); // Encountered a bug using the builtin ObjectConverter
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.PropertyName)
            {
                var name = reader.GetString();
                reader.ReadAndAssert();
                if (TryGetFixedKeyReadMethod(name, options, out var method))
                {
                    method(ref reader, obj, name, options);
                }
                else
                {
                    if (dictionary == null)
                        SetDictionary(obj, dictionary = new TDictionary());
                    dictionary.Add(name, valueConverter.ReadOrDeserialize(ref reader, typeof(TValue), options));
                }
            }
            else if (reader.TokenType == JsonTokenType.EndObject)
            {
                return obj;
            }
            else
            {
                throw new JsonException(string.Format("Unepected token {0}", reader.TokenType));
            }
        }
        throw new JsonException(); // Truncated file
    }

    public override void Write(Utf8JsonWriter writer, TObject value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        var dictionary = GetDictionary(value);
        if (dictionary != null)
        {
            var valueConverter = (typeof(TValue) == typeof(object) ? null : (JsonConverter<TValue>)options.GetConverter(typeof(TValue))); // Encountered a bug using the builtin ObjectConverter
            foreach (var pair in dictionary)
            {
                // TODO: handle DictionaryKeyPolicy 
                writer.WritePropertyName(pair.Key);
                valueConverter.WriteOrSerialize(writer, pair.Value, typeof(TValue), options);
            }
        }
        foreach (var pair in GetFixedKeyWriteMethods(options))
        {
            writer.WritePropertyName(pair.Key);
            pair.Value(writer, value, options);
        }
        writer.WriteEndObject();
    }
}

public static partial class JsonExtensions
{
    public static void WriteOrSerialize<T>(this JsonConverter<T> converter, Utf8JsonWriter writer, T value, Type type, JsonSerializerOptions options)
    {
        if (converter != null)
            converter.Write(writer, value, options);
        else
            JsonSerializer.Serialize(writer, value, type, options);
    }

    public static T ReadOrDeserialize<T>(this JsonConverter<T> converter, ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => converter != null ? converter.Read(ref reader, typeToConvert, options) : (T)JsonSerializer.Deserialize(ref reader, typeToConvert, options);

    public static void ReadAndAssert(this ref Utf8JsonReader reader)
    {
        if (!reader.Read())
            throw new JsonException();
    }
}

And now you will be able to deserialize to MarsWheatherRootObject as follows:

var root = await System.Text.Json.JsonSerializer.DeserializeAsync<MarsWheatherRootObject>(
    stream, 
    new System.Text.Json.JsonSerializerOptions 
    { 
        PropertyNameCaseInsensitive = true 
    });

Demo fiddle #1 here.

Notes:

  • FixedAndvariablePropertyNameObjectConverter<TObject, TDictionary, TValue> provides a general framework for serializing and deserializing objects with fixed and variable properties. If later you decide to deserialize e.g. "sol_keys", you could modify MarsWheatherRootObject as follows:

    [JsonConverter(typeof(MarsWheatherRootObjectConverter))]
    public class MarsWheatherRootObject
    {
        public Dictionary<string, MarsWheather> MarsWheathers { get; } = new Dictionary<string, MarsWheather>();
        public List<string> SolKeys { get; set; } = new List<string>();
    }
    

    And the converter as follows:

    public class MarsWheatherRootObjectConverter : FixedAndvariablePropertyNameObjectConverter<MarsWheatherRootObject, Dictionary<string, MarsWheather>, MarsWheather>
    {
        static readonly Dictionary<string, ReadFixedKeyMethod> FixedKeyReadMethods = new(StringComparer.OrdinalIgnoreCase)
        {
            { "sol_keys", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => 
                {
                    obj.SolKeys = JsonSerializer.Deserialize<List<string>>(ref reader, options);
                } 
            },
            { "validity_checks", (ref Utf8JsonReader reader, MarsWheatherRootObject obj, string name, JsonSerializerOptions options) => reader.Skip() },
        };
        static readonly Dictionary<string, WriteFixedKeyMethod> FixedKeyWriteMethods = new Dictionary<string, WriteFixedKeyMethod>()
        {
            { "sol_keys", (w, v, o) => 
                {
                    JsonSerializer.Serialize(w, v.SolKeys, o);
                } 
            },
        };
    
        protected override Dictionary<string, MarsWheather> GetDictionary(MarsWheatherRootObject obj) => obj.MarsWheathers;
        protected override void SetDictionary(MarsWheatherRootObject obj, Dictionary<string, MarsWheather> dictionary) => throw new RowNotInTableException();
        protected override bool TryGetFixedKeyReadMethod(string name, JsonSerializerOptions options, out ReadFixedKeyMethod method) => FixedKeyReadMethods.TryGetValue(name, out method);
        protected override IEnumerable<KeyValuePair<string, WriteFixedKeyMethod>> GetFixedKeyWriteMethods(JsonSerializerOptions options) => FixedKeyWriteMethods;
    }
    

    Demo fiddle #2 here.

🌐
Microsoft Learn
learn.microsoft.com › en-us › answers › questions › 632627 › error-from-system-text-json-trying-to-deserialize
Error from System.Text.Json trying to deserialize a single string - Microsoft Q&A
await JsonSerializer.DeserializeAsync<IEnumerable<T>>(await _client.GetStreamAsync(RelativeUri), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }) Again, with T being a string. ... A collection is a valid C# type that can be deserialized. Pass a List<string> to the generic method. ... Great! Thanks! ... Your issue is the string is not valid json. A json string has quotes around it. Ex ... So your server is not returning json. The client code should check the content-type. It’s probably text, not json.
🌐
DevGenius
blog.devgenius.io › system-text-json-in-net-7-and-deserialization-edb2b65d0a9
System.Text.Json in .NET 7 and Deserialization | by Cosmin Vladutu | Dev Genius
October 16, 2023 - JsonSerializerOptions options = new() { TypeInfoResolver = new PrivateConstructorContractResolver(), }; var professional = JsonSerializer.Deserialize<Professional>(expectedProfessionalJson,options); The solution for the second problem is not a “nice” one. It seems Microsoft is actually pushing not to use “private set” and use “init” for .NET7, but if you still want to do this, you need to annotate your properties with the [JsonInclude] attribute
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › standard › serialization › system-text-json › required-properties
Require properties for deserialization - .NET | Microsoft Learn
October 24, 2024 - To specify that all non-optional constructor parameters are required for JSON deserialization, set the JsonSerializerOptions.RespectRequiredConstructorParameters option (or, for source generation, RespectRequiredConstructorParameters property) to true.
Top answer
1 of 6
81
  1. The new System.Text.Json api exposes a JsonConverter api which allows us to convert the type as we like.

    For example, we can create a generic number to string converter:

    public class AutoNumberToStringConverter : JsonConverter<object>
    {
        public override bool CanConvert(Type typeToConvert)
        {
            return typeof(string) == typeToConvert;
        }
        public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if(reader.TokenType == JsonTokenType.Number) {
                return reader.TryGetInt64(out long l) ?
                    l.ToString():
                    reader.GetDouble().ToString();
            }
            if(reader.TokenType == JsonTokenType.String) {
                return reader.GetString();
            }
            using(JsonDocument document = JsonDocument.ParseValue(ref reader)){
                return document.RootElement.Clone().ToString();
            }
        }
    
        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        {
            writer.WriteStringValue( value.ToString());
        }
    }
    
  2. When working with MVC/Razor Page, we can register this converter in startup:

    services.AddControllersWithViews().AddJsonOptions(opts => {
        opts.JsonSerializerOptions.PropertyNameCaseInsensitive= true;
        opts.JsonSerializerOptions.Converters.Insert(0, new AutoNumberToStringConverter());
    });
    

    and then the MVC/Razor will handle the type conversion automatically.

  3. Or if you like to control the serialization/deserialization manually:

    var opts = new JsonSerializerOptions {
        PropertyNameCaseInsensitive = true,
    };
    opts.Converters.Add(new AutoNumberToStringConverter());
    var o = JsonSerializer.Deserialize<Product>(json,opts) ;
    
  4. In a similar way you can enable string to number type conversion as below :

    public class AutoStringToNumberConverter : JsonConverter<object>
    {
        public override bool CanConvert(Type typeToConvert)
        {
            // see https://stackoverflow.com/questions/1749966/c-sharp-how-to-determine-whether-a-type-is-a-number
            switch (Type.GetTypeCode(typeToConvert))
            {
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Single:
                return true;
                default:
                return false;
            }
        }
        public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if(reader.TokenType == JsonTokenType.String) {
                var s = reader.GetString() ;
                return int.TryParse(s,out var i) ? 
                    i :
                    (double.TryParse(s, out var d) ?
                        d :
                        throw new Exception($"unable to parse {s} to number")
                    );
            }
            if(reader.TokenType == JsonTokenType.Number) {
                return reader.TryGetInt64(out long l) ?
                    l:
                    reader.GetDouble();
            }
            using(JsonDocument document = JsonDocument.ParseValue(ref reader)){
                throw new Exception($"unable to parse {document.RootElement.ToString()} to number");
            }
        }
    
    
        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        {
            var str = value.ToString();             // I don't want to write int/decimal/double/...  for each case, so I just convert it to string . You might want to replace it with strong type version.
            if(int.TryParse(str, out var i)){
                writer.WriteNumberValue(i);
            }
            else if(double.TryParse(str, out var d)){
                writer.WriteNumberValue(d);
            }
            else{
                throw new Exception($"unable to parse {str} to number");
            }
        }
    }
    
2 of 6
29

If you only want to deserialize strings as numbers, you could simply set the NumberHandling property to AllowReadingFromString in the options:

var o = JsonSerializer.Deserialize<Product>(json, new JsonSerializerOptions
{
    // [...]
    NumberHandling = JsonNumberHandling.AllowReadingFromString
});

Note: This will automatically deserialize strings as numbers, but not numbers as string. If you need more advanced logic, you will need a custom converter (see other answers).