Search code examples
c#base64jsonserializersystem.text.json

Deserialization with nested object and in-between step


I am attempting to serialize/deserialze an object using the System.Text.Json JsonSerializer. My container object is a "LicenseFile" which contains a "License" object along with a byte[] digital signature.

public class LicenseFile
{
    public License License { get; set; }
    public byte[] Signature { get; set; }
}

public class License
{
    public string ProductName { get; set; }
    public string ProductVersion { get; set; }
}

When serializing the LicenseFile, I would also like to first convert the License values to JSON and afterwards Base64. To do this, I created a custom JSON converter e.g.

public class LicenseFileConverter : JsonConverter<LicenseFile>
{
    public override void Write(Utf8JsonWriter writer, LicenseFile licenseFile, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        var json = JsonSerializer.Serialize(licenseFile.License);
        byte[] jsonBytes = new UTF8Encoding().GetBytes(json);
        writer.WriteBase64String("License", jsonBytes);
        writer.WriteBase64String("Signature", licenseFile.Signature);
        writer.WriteEndObject();
        writer.Flush();
    }
}

I would like to end up with a JSON output something like this:

{
  "License": "BASE64_OF_LICENSE_OBJECT_JSON'D",
  "Signature": "BASE64_OF_SIGNATURE_BYTE[]"
}

My questions:

  1. Is this a good approach? Would I better off to just use helper methods to serialize the values first, base64 them and then write them out to a file?
  2. How can I deserialze the JSON object back into objects again (while de-base64'ing them on the way)

Thanks for any recommendations!


Solution

  • I recommend:

    1. a dedicated Dto; or
    2. [JsonIgnore] on License and an auxiliary field;

    For example:

    public class LicenseFile
    {
        [JsonIgnore]
        public License License { get; set; }
    
        public string LicenseJson => JsonSerializer.Serialize(License); // Add base64 encoding if you wish
    
        public byte[] Signature { get; set; }
    }
    

    or

    public class LicenseFileDto
    {
        public LicenseFileDto(LicenseFile license) {
            License = JsonSerializer.Serialize(license.License);
            Signature = license.Signature;
        }
        public string License { get; }
        public byte[] Signature { get; }
    }
    

    Test

    var l = new LicenseFile
    {
        License = new License(name: "X Y", validUntil: new DateTime(2021, 04, 22)),
        Signature = new byte[] { 1, 2, 3 }
    };
    
    var json = JsonSerializer.Serialize(l, 
        options: new JsonSerializerOptions { WriteIndented = true });
    Console.WriteLine(json);
    
    var dtoJson = JsonSerializer.Serialize(new LicenseFileDto(l), 
        options: new JsonSerializerOptions { WriteIndented = true });
    Console.WriteLine(dtoJson);            
    
    // Documentation at:
    //   https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-encoding#serialize-all-characters
    // says: 
    //     Use the unsafe encoder only when it's known that the client will be interpreting the resulting payload as UTF-8 encoded JSON.
    var prettyJson = JsonSerializer.Serialize(new LicenseFileDto(l), 
        options: new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
    Console.WriteLine(prettyJson);
    
    {
      "LicenseJson": "{\u0022Name\u0022:\u0022X Y\u0022,\u0022ValidUntil\u0022:\u00222021-04-22T00:00:00\u0022}",
      "Signature": "AQID"
    }
    {
      "License": "{\u0022Name\u0022:\u0022X Y\u0022,\u0022ValidUntil\u0022:\u00222021-04-22T00:00:00\u0022}",
      "Signature": "AQID"
    }
    {
      "License": "{\"Name\":\"X Y\",\"ValidUntil\":\"2021-04-22T00:00:00\"}",
      "Signature": "AQID"
    }