C
C#2y ago
Becquerel

Calculating length of a Span of bytes when encoding text [Answered]

I need a method that takes text, encodes it and converts it to base 64 without allocations (if possible). This is what I have so far:
private Span<char> ToBase64(Span<char> text)
{
// _encoding is UTF-8

// get how many bytes the text will take up when encoded
var byteCount = _encoding.GetByteCount(text);

// alloc array with that count so we can get a Span<byte>
Span<byte> span = stackalloc byte[byteCount];

// write the encoded text into the byte span
_ = _encoding.GetBytes(text, span);

// use the encoded byte span to actually go to base64
// _encoder is a nuget package that asks for an ArraySegment<char>
return _encoder.ToBaseNonAlloc(span.ToArray());
}
private Span<char> ToBase64(Span<char> text)
{
// _encoding is UTF-8

// get how many bytes the text will take up when encoded
var byteCount = _encoding.GetByteCount(text);

// alloc array with that count so we can get a Span<byte>
Span<byte> span = stackalloc byte[byteCount];

// write the encoded text into the byte span
_ = _encoding.GetBytes(text, span);

// use the encoded byte span to actually go to base64
// _encoder is a nuget package that asks for an ArraySegment<char>
return _encoder.ToBaseNonAlloc(span.ToArray());
}
I'm using this to encode/decode JSON. I have a unit test for roundtripping (encode/decode the same object and check it's equivalent) this that is failing.
private class MyObject
{
public string Name { get; set; }
public int Age { get; set; }
public bool Alive { get; set; }
}

[Fact]
public void Encode_ShouldRoundTrip()
{
var obj = new MyObject
{
Name = "John Doe",
Age = 32,
Alive = true
};

var json = JsonConvert.SerializeObject(obj);

// _encoder here is my own class where the above method lives
var encoded = _encoder.Encode(json);
var unencoded = _encoder.Decode(encoded);

var newObj = JsonConvert.DeserializeObject<MyObject>(unencoded);

newObj.Should().BeEquivalentTo(obj);
}
private class MyObject
{
public string Name { get; set; }
public int Age { get; set; }
public bool Alive { get; set; }
}

[Fact]
public void Encode_ShouldRoundTrip()
{
var obj = new MyObject
{
Name = "John Doe",
Age = 32,
Alive = true
};

var json = JsonConvert.SerializeObject(obj);

// _encoder here is my own class where the above method lives
var encoded = _encoder.Encode(json);
var unencoded = _encoder.Decode(encoded);

var newObj = JsonConvert.DeserializeObject<MyObject>(unencoded);

newObj.Should().BeEquivalentTo(obj);
}
It decodes the actual JSON correctly, but then continues onwards, filling up the string with garbage data that causes deserialization to explode. {"Name":"John Doe","Age":32,"Alive":truep4��bY,P�&��4]�!�V��!�]D��aQ<@0XbUh2VaplSHNXUThGbhhUTwQQP==
11 Replies
Becquerel
Becquerel2y ago
My assumption is that I'm messing up how big the Span<byte> should be and that's causing it to include garbage data, but I don't see what I'm doing wrong. I am doing other stuff besides just encoding the JSON that I can explain if all of that looks OK, but wanted to check this first since I've not messed with spans before.
MKP
MKP2y ago
check the Span<byte>'s [length] to make sure its null terminated
Becquerel
Becquerel2y ago
doesn't appear so
Becquerel
Becquerel2y ago
should I set its length to byteCount + 1 and ensure the last element is null?
MKP
MKP2y ago
yeah
Becquerel
Becquerel2y ago
what's the correct syntax for that - span[^1] = null; complains that I can't assign null to byte or is it fine for it to be a Span<byte?>?
MKP
MKP2y ago
0
Becquerel
Becquerel2y ago
ah, got you - makes sense tried that and i'm still getting garbage data added in so i think i need to check the rest of my implementation figured it out, in my method where I parsed the base64, I was writing back into the same span<char> I was reading from 😄
MKP
MKP2y ago
do /close
Becquerel
Becquerel2y ago
mhmm
Accord
Accord2y ago
✅ This post has been marked as answered!