2

I am using System.Text.Json for deserialization.

I want to use the constructor in SerialNo(string serialNo) to build my object.

public class SerialNo
{
    [JsonConstructor]
    public SerialNo(string serialNo)
    {
        if (serialNo == null) throw new ArgumentNullException(nameof(serialNo));

        if (string.IsNullOrWhiteSpace(serialNo)) throw new Exception("My exception text");

        Value = serialNo.Trim('0');
    }

    public string Value { get; set; }
}

public class Item
{
    public SerialNo SerialNo { get; set; }

    public string AffiliationOrgCode { get; set; }
}

public class Root
{
    public List<Item> Item { get; set; }
}

public class DeserializationTestsWithSystemTextJson
{
    private const string JsonString = @"
        {
            ""item"": [
              {
                ""serialNo"": ""000000000002200878"",
                ""affiliationOrgCode"": ""OrgCode1""
              },
              {
                ""serialNo"": ""000000000002201675"",
                ""affiliationOrgCode"": ""OrgCode1""
              }
            ]
        }
    ";

    [Fact]
    public void Simple_Deserialization_With_SystemTextJson()
    {
        var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
        var deserializedClass = JsonSerializer.Deserialize<Root>(JsonString, options);

        Assert.NotNull(deserializedClass);
        Assert.Equal("2201675", deserializedClass.Item[1].SerialNo.Value);
    }
}

Fails with:

The JSON value could not be converted to JsonNetDeserialization.Test.WithSystemText.SerialNo. Path: $.item[0].serialNo | LineNumber: 3 | BytePositionInLine: 44.

Any ideas?

2 Answers 2

5

Since you are converting a string to a SerialNo, you need a custom converter. For example:

public class SerialNoConverter : JsonConverter<SerialNo>
{
    public override SerialNo? Read(ref Utf8JsonReader reader, Type typeToConvert, 
        JsonSerializerOptions options)
    {
        var serialNo = reader.GetString();
        return new SerialNo(serialNo);
    }

    public override void Write(Utf8JsonWriter writer, SerialNo value, 
        JsonSerializerOptions options)
    {
        // Left for OP if required
        throw new NotImplementedException();
    }
}

And to use it you can either add an attribute to your class:

public class Item
{
    [JsonConverter(typeof(SerialNoConverter))]
    public SerialNo SerialNo { get; set; }

    public string AffiliationOrgCode { get; set; }
}

Or add the converter to your serialiser options:

options.Converters.Add(new SerialNoConverter());

Here is a running example.

Sign up to request clarification or add additional context in comments.

2 Comments

When using JsonConverter then you don't need JsonConstructor.
I know, but I left that in in case OP needs it for something else.
1

You are able to use a constructor to deserialize JSON using System.Text.Json, contrary to what the marked answer says. Even Microsoft documents it: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/immutability?pivots=dotnet-7-0

There are a some drawbacks though:

  1. All class/struct fields/properties must match the JSON property name exactly
  2. All json properties must not be nested in a deeper JSON object

To deserialize JSON with a constructor, you just need to use [JsonConstructor]. Here's an example of deserializing this way:

JSON:

{
  "Fp": 2.199,
  "Pi": 3.14159,
  "Str": "Hello World"
}

The deserialized class:

public class Dto
{
    public double Fp { get; set; }
    public double Pi { get; set; }
    public string Str { get; set; }

    [JsonConstructor]
    public Dto(double fp, double pi, string str)
    {
        Fp = fp;
        Pi = pi;
        Str = str;
    }

    public override string ToString()
    {
        return $"{nameof(Fp)}: {Fp}, {nameof(Pi)}: {Pi}, {nameof(Str)}: {Str}";
    }
}

Now to deserialize, you just include this expression: var dto = JsonSerializer.Deserialize<Dto>(Json); In the event you are trying to deserialize are custom type, you can include the [JsonConverter(...)] attribute over the custom type properties/fields in your deserialized class if the custom types do not already have a JsonConverter attribute for their class. Because the class properties use the exact name as in the Json, all other types are still deserialized properly and the custom type property will use the attributed custom converter. Here is an example of using a custom converter:

public class Dto
{
    public double Fp { get; set; }
    public double Pi { get; set; }
    [JsonConverter(typeof(CustomStringConverter))] // This can be removed if the CustomString class already has a JsonConverter attribute
    public CustomString Str { get; set; }
    
    [JsonConstructor]
    public Dto(double fp, double pi, CustomString str)
    {
        this.Fp = fp;
        Pi = pi;
        Str = str;
    }

    public override string ToString()
    {
        return $"{nameof(Fp)}: {Fp}, {nameof(Pi)}: {Pi}, {nameof(Str)}: {Str}";
    }
}

4 Comments

yes but, in your example the DTO is for a JSON Object and not a JSON string.
In the usage expression, Json is the json example which could be represented as multiple types from strings and streams to UTF8JsonReaders and JsonDocuments. It's up to the user to decide that
The question is, how would you customize the type of Str so that it is not string but a custom type?
You would just create a converter for the custom type and put the attribute over the custom property. If you were to replace the string Str with CustomString Str, all other types would still be deserialized and the custom converter would be used for Custom String. I'm going to edit my answer to reflect this, thanks for that heads-up that I forgot to include

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.