Skip to content

System.Text.Json absent empty constructor exception on class, none for struct #35161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
NinoFloris opened this issue Apr 18, 2020 · 5 comments
Closed

Comments

@NinoFloris
Copy link
Contributor

NinoFloris commented Apr 18, 2020

System.Text.Json (netcore3.1) missing empty constructor exception on class, none for struct during deserialization.

public class Item 
{
    public Item(int value) => Value = value;
    public int Value { get; }
}

public struct ValueItem
{
    public ValueItem(int value) => Value = value;
    public int Value { get; }
}

JsonSerializer.Deserialize<Item>("{\"Value\": 1}"); // exception
JsonSerializer.Deserialize<ValueItem>("{\"Value\": 1}"); // no exception

It took me a while before I understood why none of my structs were getting filled with data.

From a type system perspective It makes sense as structs always have a default constructor, though it seems when there is actually data the serializer should throw an exception just like the class case.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Apr 18, 2020
@ghost
Copy link

ghost commented Apr 18, 2020

Tagging subscribers to this area: @jozkee
Notify danmosemsft if you want to be subscribed.

@layomia
Copy link
Contributor

layomia commented Apr 21, 2020

it seems when there is actually data the serializer should throw an exception just like the class case?

Why should an exception be thrown in this case, and why should it be dependent on if there's JSON data?

FWIW, a new serializer feature allowing deserialization with parameterized ctors was recently added - #33444.

@layomia layomia added this to the 5.0 milestone Apr 21, 2020
@layomia layomia removed the untriaged New issue has not been triaged by the area owner label Apr 21, 2020
@NinoFloris
Copy link
Contributor Author

NinoFloris commented Apr 21, 2020

@layomia because you actually end up with a default struct instead of a struct with data, this is dev hostile.

For instance in F# making a struct record like so

[<Struct>]
type Foo = { Bar: string }

Will cause the serializer to never throw and no data is ever set. You have to change the type declaration to

[<CLIMutable;Struct>]
type Foo = { Bar: string }

This adds setters to all properties so data can be set.

Now I expect this issue to have better results with #33444 because obviously fsc (F# compiler) does emit a full constructor in both cases. Though I'm not sure what STJ uses as the constructor selection algorithm from #33444 onwards (is it based on incoming data, or will it pick say the largest constructor and push default values for missing properties?)

@layomia
Copy link
Contributor

layomia commented Apr 21, 2020

because you actually end up with a default struct instead of a struct with data, this is dev hostile.

The serializer throws an exception in the class case is because it did not find a public parameterless ctor to use, not because the property is read-only. Deserializing structs will never fail for this reason; as you noted, they'll always have a parameterless ctor. So, the exception thrown here has nothing to do with constructors.

It is generally known that the serializer won't deserialize into a read-only property as there's no setter visible. Devs should keep this in mind when passing types to the serializer.

If you need validation that a property is set on deserialization as a feature, you can see if the feature proposed in #29861 ("implement a concept of required properties") suffices, or you can create a new issue to propose exception semantics specific to property deserialization.

Workarounds to get the serializer to set data for a getter-only property:

  • add a setter, as you've done above
  • utilize the deserialization-with-parameterized ctors feature
  • implement a custom converter for the property

Though I'm not sure what STJ uses as the constructor selection algorithm from #33444 onwards (is it based on incoming data, or will it pick say the largest constructor and push default values for missing properties?)

The serializer will use:

  • a public ctor annotated with [JsonConstructor]. If the attribute is not used:
  • a public parameterless ctor. If not present, the serializer will use:
  • a singular public parameterized ctor (if no other public ctor is present)

If none of these is present, a NotSupportedException is thrown with a message that there was no ctor to use. See the feature spec for more detail. We'll provide documentation for this in the official .NET JSON docs shortly.

I'll close this as this issue is not actionable as it currently reads.

@layomia layomia closed this as completed Apr 21, 2020
@NinoFloris
Copy link
Contributor Author

Thanks for the elaboration!

@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants