0

I'm working with a POCO class in .NET 5.0 that I need to serialize and deserialize to and from the browser, and this is in a library where I don't want to have to tell the users which serializers they must use.

So I'm trying to make it work with both Newtonsoft.Json and System.Text.Json.

My problem is that values stored in fields with type object aren't being deserialized into their original type, in System.Text.Json.

using Shouldly;
using Xunit;
using SJ = System.Text.Json;
using NJ = Newtonsoft.Json;

public class SerializationTests
{
    [Fact]
    public void TestNJ()
    {
        var nvp = new NVP { Name = "a name", Value = "a value" };

        var s = NJ.JsonConvert.SerializeObject(nvp);
        var d = NJ.JsonConvert.DeserializeObject<NVP>(s);
        d.ShouldBeEquivalentTo(nvp);
    }

    [Fact]
    public void TestSJ()
    {
        var nvp = new NVP { Name = "a name", Value = "a value" };

        var s = SJ.JsonSerializer.Serialize(nvp);
        var d = SJ.JsonSerializer.Deserialize<NVP>(s);
        d.ShouldBeEquivalentTo(nvp);
    }
}

TestNJ passes.

TestSJ throws an exception:

Expected value to be System.String but was System.Text.Json.JsonElement'

In the debugger it's clear what's going on.

In both TestNJ() and TestSJ(), at the beginning, nvp has the value:

nvp: {NVP}
  Name [string]: "a name"
  Value [object]: " a value"

In both, after serialization, s has the value I'd expect:

"{\"Name\":\"a name\",\"Value\":\"a value\"}"

But where in TestNJ(), after deserialization, d has the value:

d: {NVP}
  Name [string]: "a name"
  Value [object]: " a value"

In TestSJ(), d has the value:

d: {NVP}
  Name [string]: "a name"
  Value [object]: ValueKind = String: "a value"

What is going on here? How do I get System.Text.Json to actually deserialize this type?

dbc
  • 104,963
  • 20
  • 228
  • 340
Jeff Dege
  • 11,190
  • 22
  • 96
  • 165
  • 1
    Looks to me like this Github issue is the same. [deserialize object prop adds Valuekind to value #31408](https://github.com/dotnet/runtime/issues/31408) – Dennis VW Jul 25 '21 at 17:09
  • You could apply `[JsonConverter(typeof(ObjectAsPrimitiveConverter))]` to `Value` where `ObjectAsPrimitiveConverter` comes from [this answer](https://stackoverflow.com/a/65974452/3744182) to [C# - Deserializing nested json to nested Dictionary](https://stackoverflow.com/q/65972825/3744182). – dbc Aug 15 '21 at 16:15
  • That won't help you with `DateTime` though. JSON doesn't have a primitive type for dates and times so they simply get serialized as strings. Json.NET and `DataContractSerializer` have logic that attempts to infer if a string was a date -- but it is better not to rely on such logic since a user-entered string might look like a `DateTime` string. So your DTO solution looks better. – dbc Aug 15 '21 at 16:18

1 Answers1

3

Dennis pointed to https://github.com/dotnet/runtime/issues/31408 And that was closed as a duplicate of https://github.com/dotnet/runtime/issues/29960.

So the answer is, you don't.

From the discussion on GitHub:

This is by design (and unlikely to change). Since the object type could map the JSON payload to any .NET type, while deserializing, the JsonSerializer returns a boxed JsonElement instead that points to the JSON token itself. We don't try to infer or guess the .NET type from the JSON payload during deserialization and hence, the caller (who has enough context) would need to turn the returned JsonElement into the expected .NET type.

If you are trying to what I am trying to do, create a POCO class in a utility library that can be serialized and deserialized by clients of the library without requiring any special effort on the users of the library, don't use object.

In my case, I have a field that might contain a string, int, double, decimal, or datetime. I'm going to have to replace that with a separate nullable field for each.

Jeff Dege
  • 11,190
  • 22
  • 96
  • 165
  • I've tried it both ways, using fields of individual types and using a single nullable field of each supported type. I found that even with separate fields, I still needed custom converters. And the problem with using separate fields is that then the code that uses these values needs to know about all of the separate fields. – Jeff Dege Aug 15 '21 at 17:05
  • ew! I thought this was supposed to be a replacement for Newtonsoft. For the most part it's been a easy migration from Newtonsoft, but this is the most disruptive change I've noticed. – KrimblKrum Nov 30 '22 at 19:41