3

I want to convert my model to json, and it has a byte[] array that gets converted to a base64 string while converting to json. I'm not wanting to converting the byte[] array to string format.

Here is what I have tried

       MyModel myObject = new MyModel
        {
            Id = 1,                
            Data = new byte[] { 0x01, 0x02, 0x03 }
        };
        string json = JsonSerializer.Serialize(myObject, options);

It returns the output as Json string output

I want output as {"Id":[1],"Data":[1,2,3]}.

Edit: I have code for above in powershell script which do the same

$CertificateJsonString = ConvertTo-Json $CertificateVaultObject -Compress
$CertificateSecureString = ConvertTo-SecureString -String $CertificateJsonString -AsPlainText –Force
Charlieface
  • 52,284
  • 6
  • 19
  • 43
Lifewithsun
  • 968
  • 14
  • 34
  • 2
    So you get "AQID" and you expect 1,2,3. How shall that be possible? Are you sure you're looking at the output of the code you posted? Did you run an old build? Why should ID be an array (`[1]`) when it's only an `int`? – Thomas Weller May 16 '23 at 14:05
  • 1
    If you don't want your byte[] to be converted to a base64 string when serializing, then what string representation should it become? – MindSwipe May 16 '23 at 14:06
  • 1
    @ThomasWeller I have certificate byte data that I want to store somewhere in byte[] format only. It has more than 2k byte like {"CertBytes":[48,130,13,240,2,1,3,48,130,13,........ 10,144]}. I have powershell command which does the work. – Lifewithsun May 16 '23 at 14:10
  • 1
    don't know if its a clean way, but maybe use a intermediate class with data as a int[], serialize should do array of int like the ouput requested – Arnaud May 16 '23 at 14:10
  • 3
    Also, why do you think `Id = 1` should result in `"Id":[1]` not `"Id":1`? That seems to be a completely different requirement. – dbc May 16 '23 at 14:17
  • What did you do in C#, How did you define `MyModel` ? – Luuk May 16 '23 at 14:21
  • 1
    @Lifewithsun your expectations are wrong. A `byte[]` buffer is a buffer, not an array of numbers. In JSON that's represented as a BASE64 string, otherwise you'd end up with a bunch of unprintable characters, newlines, quotes etc. The byte `0x0` is the NUL character. `0x1` is an unprintable character too, not the number `1` which is the byte `0x31` – Panagiotis Kanavos May 16 '23 at 14:22
  • 1
    `I have certificate byte data that I want to store somewhere in byte[] format only` then you **must** serialize it as BASE64. When that gets deserialized you'll get the original buffer back – Panagiotis Kanavos May 16 '23 at 14:23
  • 3
    Encoding binary content as BASE64 is [part of the standard](https://datatracker.ietf.org/doc/html/rfc7493#section-4.4). Using your own custom serialization format will make it harder to work with any other application - including your own, as you'll have to repeat the customization all over the place – Panagiotis Kanavos May 16 '23 at 14:35

1 Answers1

7

System.Text.Json (and Json.NET) only serializes byte arrays as Base64, so if you declare your Data as some other collection or enumerable of bytes (but not object) it will be serialized as a JSON array, e.g:

public class MyModel
{
    public int Id { get; set; }
    public IEnumerable<byte>? Data { get; set; }
}

When serialized via:

MyModel myObject = new MyModel
{
    Id = 1,                
    Data = new byte[] { 0x01, 0x02, 0x03 }
};
string json = JsonSerializer.Serialize(myObject, options);

Results in:

{"Id":1,"Data":[1,2,3]}

Demo fiddle #1 here.

Or, if you can't change your model, you could introduce a JsonConverter<byte> that round-trips byte [] values as an array:

public class ByteArrayConverter : JsonConverter<byte []>
{
    public override void Write(Utf8JsonWriter writer, byte [] value, JsonSerializerOptions options) =>
        JsonSerializer.Serialize(writer, value.AsEnumerable());
    
    public override byte []? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        reader.TokenType switch
        {
            JsonTokenType.String => reader.GetBytesFromBase64(),
            JsonTokenType.StartArray => JsonSerializer.Deserialize<List<byte>>(ref reader)!.ToArray(),
            JsonTokenType.Null => null,
            _ => throw new JsonException(),
        };
}

And serialize like so:

MyModel myObject = new MyModel
{
    Id = 1,                
    Data = new byte[] { 0x01, 0x02, 0x03 }
};
var options = new JsonSerializerOptions { Converters = { new ByteArrayConverter() } };
string json = JsonSerializer.Serialize(myObject, options);

And get the same result.

Demo fiddle #2 here.

Notes:

dbc
  • 104,963
  • 20
  • 228
  • 340
  • If that payload is certificate data, anything other than BASE64 would be a problem – Panagiotis Kanavos May 16 '23 at 14:23
  • @PanagiotisKanavos - it seems like an odd requirement for sure, but it does come up from time to time. The equivalent question Json.NET [How to serialize `byte[]` as simple JSON Array and not as base64 in JSON.net?](https://stackoverflow.com/q/15226921) has 31 votes so I think it's a legitimate question for System.Text.Json as well. – dbc May 16 '23 at 14:27
  • @PanagiotisKanavos why? – Mike Nakis May 16 '23 at 14:29
  • 2
    @MikeNakis for starters, [it's against the standard](https://datatracker.ietf.org/doc/html/rfc7493#section-4.4) which means other applications will have trouble dealing with that non-standard field. The reason the standard recommends BASE64 is that otherwise the data type is lost. Since JSON has no explicit numeric types, that list of *numbers* can be translated as anything, including floats. There's no way to mistranslate BASE64, assuming you know what's stored in that field. An array of numbers takes far more space too. – Panagiotis Kanavos May 16 '23 at 14:37
  • @PanagiotisKanavos - I added a note mentioning your comment, linking to the standard, and suggesting using Base64 as-is. – dbc May 16 '23 at 14:51