pub struct Writer<'a, O: Options = Standard> { /* private fields */ }Expand description
Accumulates JSON output.
The lifetime is the borrow of an io::Write sink, and is 'static for
the ordinary in-memory writers built by Writer::new and friends.
O is the write policy, which decides at compile time
whether the output is indented and whether null members are left out. It
defaults to Standard, though a constructor cannot infer from that, so
build one as Writer::<Standard>::new(). Trait implementations take
&mut Writer<'_, O> and stay generic over it.
§Output only goes forwards
A writer cannot be rewound. There is no truncate, no checkpoint, no way
to drop back to a mark, and the nesting depth is not readable either. That
follows from what a writer is rather than being an omission.
Writer::to_sink hands the front of the buffer to an io::Write as it
fills, so a byte a caller wanted back may already be down a socket and
beyond recall. A checkpoint could be honoured only by refusing to drain
until it was released, which is buffering without a bound, and that is the
one property the sink exists to provide.
The in-memory writer could offer it, and deliberately does not. A
Write impl is handed &mut Writer<'_, O> and cannot
tell the two apart, so an operation that worked on one and quietly held a
stream’s whole output in memory on the other would be worse than no
operation: the failure would show up as a machine running out of memory
under load, in an impl whose author had no way to know it was doing that.
What this asks of an implementation is that it settle a question before
emitting its first byte rather than part way through. A type that might have
to write something other than what it started on walks its input first and
commits afterwards. Raw is the worked example: it may
have to fall back to emitting its span verbatim, so under
Options::PRETTY a probe pass walks the span and settles whether it lays
out at all, and only then does
prettify_value_into emit anything. The
probe costs a second walk over the span; a fallback discovered part way
through the layout would write the value twice, and no arrangement of the
code makes that recoverable.
Implementations§
Source§impl<O: Options> Writer<'static, O>
impl<O: Options> Writer<'static, O>
pub fn new() -> Self
pub fn with_capacity(n: usize) -> Self
Sourcepub fn from_vec(buf: Vec<u8>) -> Self
pub fn from_vec(buf: Vec<u8>) -> Self
Reuse an existing buffer, discarding whatever it held.
Self::appending is the one that keeps it.
Sourcepub fn appending(buf: Vec<u8>) -> Self
pub fn appending(buf: Vec<u8>) -> Self
Write after what a buffer already holds, rather than over it.
Self::from_vec discards the contents; this keeps them and writes
past them. A document that has to sit behind something – a protocol
header, or the entries already written into a listing – then costs one
buffer rather than a second buffer and a copy out of it.
json::append is this with the writer kept out
of sight, and beve::Writer::appending
is the same constructor on the other format.
The bytes in front are not examined and need not be text, since what
comes back from Self::into_vec is bytes. They are checked, rather
than trusted, by Self::into_string, which is what makes appending
onto the bytes of a String sound:
use structio::Standard;
use structio::json::{Write, Writer};
let out = String::from("[1,2,");
let mut w = Writer::<Standard>::appending(out.into_bytes());
3.write(&mut w);
let mut out = w.into_string();
out.push(']');
assert_eq!(out, "[1,2,3]");Source§impl<'a, O: Options> Writer<'a, O>
impl<'a, O: Options> Writer<'a, O>
Sourcepub fn to_sink(out: &'a mut dyn Write) -> Self
pub fn to_sink(out: &'a mut dyn Write) -> Self
Write through to out, buffering DEFAULT_SINK_BUFFER bytes at a
time.
The document is drained as it is produced, so peak memory is the buffer
plus the largest single scalar written, not the size of the output.
Writer::finish must be called to flush the tail and report any I/O
error; crate::to_writer does both.
Sourcepub fn to_sink_with_capacity(out: &'a mut dyn Write, capacity: usize) -> Self
pub fn to_sink_with_capacity(out: &'a mut dyn Write, capacity: usize) -> Self
Writer::to_sink with an explicit buffer size.
capacity is clamped up to one byte: draining always retains the last
byte written, because it may still be a trailing comma awaiting
overwrite by a closing brace.
Sourcepub fn as_bytes(&self) -> &[u8] ⓘ
pub fn as_bytes(&self) -> &[u8] ⓘ
The bytes written so far, or with a sink, the bytes not yet drained.
With Writer::appending the bytes handed in are in front of them,
those being in the buffer too. This is the whole buffer either way,
which is what Writer::into_vec hands back.
Sourcepub fn into_string(self) -> String
pub fn into_string(self) -> String
Take the output.
Everything written is valid UTF-8 by construction: string contents come
from &str, numbers and escapes are ASCII, and Writer::push rejects
non-ASCII bytes.
With a sink this returns only the undrained tail. Use
Writer::finish instead.
§Panics
If the writer was built by Writer::appending over bytes that are
not valid UTF-8. Those are the only bytes in the buffer this writer did
not produce, so they are the only ones it has to check; a binary prefix
is a perfectly good thing to append JSON behind, but it cannot come
back out as a String. Take Writer::into_vec there instead.
pub fn into_vec(self) -> Vec<u8> ⓘ
Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Bytes currently buffered.
With a sink this counts only what has not been drained, matching
Writer::as_bytes. A running total of the whole document is not
offered because a failed drain would make it a lie. With
Writer::appending it counts the bytes handed in as well, those
being in the buffer.
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Whether the buffer holds nothing.
Writer::len’s companion, and it counts what that counts: a writer
built by Writer::appending over a buffer with something in it is
not empty before it writes a byte of its own.
Sourcepub fn finish(self) -> Result<()>
pub fn finish(self) -> Result<()>
Drain the remaining bytes to the sink and report the first I/O error.
Without a sink this is Ok(()) and does nothing.
By value on purpose. A sink writer that keeps going after its tail has been flushed would emit a second document’s worth of bytes into the middle of the first, and asking twice whether the write succeeded has no second answer worth giving.
Sourcepub fn push(&mut self, b: u8)
pub fn push(&mut self, b: u8)
Append one ASCII byte.
§Panics
If b is not ASCII. Writer::into_string converts without
re-validating, so the buffer has to stay valid UTF-8, and this is the
only way safe code could break that. At every internal call site b is
a literal, so the check folds away.
Sourcepub fn raw(&mut self, s: &str)
pub fn raw(&mut self, s: &str)
Append a string verbatim, without quoting or escaping it.
Whatever s holds becomes part of the document as written, so keeping
the result valid JSON is the caller’s job. Emitting a number literal is
a supported use of this, and
write_number_str is that use with the
literal checked.
Sourcepub fn write_object<T: WriteObject>(&mut self, value: &T)
pub fn write_object<T: WriteObject>(&mut self, value: &T)
Write a struct as a JSON object.
Members are written with an unconditional trailing comma, and the last
one is overwritten with }. That removes the per-field “am I first”
branch from the inner loop entirely.
The overwrite checks for that comma rather than assuming it.
WriteObject is a safe trait that anyone may implement, so nothing
guarantees write_fields wrote what it was asked to, and the buffer is
handed out by Writer::into_string without revalidation.
Sourcepub fn member<T: Write + ?Sized>(&mut self, prefix: &str, value: &T)
pub fn member<T: Write + ?Sized>(&mut self, prefix: &str, value: &T)
Write one "key":value, member. prefix is the pre-quoted key with its
colon, built at compile time by the macro.
The prefix is written as it is given, quotes, colon and all, and
nothing in it is escaped: the macro builds it from a Rust identifier,
which has nothing JSON needs to escape. A key computed at runtime has no
such guarantee, so pass it to Self::member_key and let this crate
quote it rather than assembling the prefix by hand.
Under Options::SKIP_NULL a member holding nothing is not written at
all, key included. The test is a constant plus a call that is false
for all but a handful of types, so a policy that does not ask for it
pays nothing and one that does pays a predictable branch on a value
already in hand.
Sourcepub fn member_with<A: WriteAs<T>, T: ?Sized>(&mut self, prefix: &str, value: &T)
pub fn member_with<A: WriteAs<T>, T: ?Sized>(&mut self, prefix: &str, value: &T)
Write one "key":value, member, the value through an adapter.
Self::member for a field whose declaration named an adapter, down to
the trailing comma and to Options::SKIP_NULL, which asks
WriteAs::is_null rather than the value itself. A member that writes
itself would escape both.
A appears in no argument, so it is always turned up explicitly:
w.member_with::<Millis, _>(prefix, value).
Sourcepub fn member_key<T: Write + ?Sized>(&mut self, key: &str, value: &T)
pub fn member_key<T: Write + ?Sized>(&mut self, key: &str, value: &T)
Write one "key":value, member, the key given at runtime and quoted
here.
Self::member takes a prefix already quoted and punctuated, which the
macro assembles from a Rust identifier through
quoted_key: a key with nothing in it for JSON to escape. A writer
that discovers its keys as it goes has no such guarantee, and building
the prefix itself means escaping by hand or emitting a document no
reader will take. This takes the key alone and escapes it exactly as
Self::write_str does.
Options::SKIP_NULL applies, as it does to any struct member. Where
the key came from is not what the policy turns on: the boundary it
draws is between a struct’s member and a map’s entry, and
Self::write_keyed is the map.
use structio::json::{WriteObject, Writer};
use structio::{KeyMap, Keys, Options, Standard};
struct Walked<'a>(&'a [(String, u32)]);
impl Keys for Walked<'_> {
// No declaration, so no static key set and no read half.
const KEYS: &'static [&'static str] = &[];
const MAP: &'static KeyMap = &KeyMap::build(Self::KEYS);
}
impl WriteObject for Walked<'_> {
fn write_fields<O: Options>(&self, w: &mut Writer<'_, O>) {
for (key, value) in self.0 {
w.member_key(key, value);
}
}
}
let entries = [("a \"quoted\" key".to_owned(), 1)];
let mut w = Writer::<Standard>::new();
w.write_object(&Walked(&entries));
assert_eq!(w.into_string(), r#"{"a \"quoted\" key":1}"#);Sourcepub fn member_key_with<A: WriteAs<T>, T: ?Sized>(
&mut self,
key: &str,
value: &T,
)
pub fn member_key_with<A: WriteAs<T>, T: ?Sized>( &mut self, key: &str, value: &T, )
Write one "key":value, member with a runtime key, the value through an
adapter.
Self::member_key as Self::member_with is to Self::member:
the key is escaped here and Options::SKIP_NULL asks
WriteAs::is_null rather than the value itself.
A appears in no argument, so it is always turned up explicitly:
w.member_key_with::<Millis, _>(key, value).
Sourcepub fn write_tagged<T: Write + ?Sized>(&mut self, prefix: &str, value: &T)
pub fn write_tagged<T: Write + ?Sized>(&mut self, prefix: &str, value: &T)
Write an enum variant that carries a value: {"Name":value}.
prefix is the pre-quoted name with its colon, built at compile time
by the macro, exactly as Self::member takes one. A variant carrying
nothing is not written here at all: it is its own name, so it goes
through Self::write_str.
Options::SKIP_NULL deliberately does not reach here. Dropping the
member would leave {}, which names no variant and so is not a smaller
spelling of this value but a different one.
Sourcepub fn write_internally_tagged<T: WriteObject + ?Sized>(
&mut self,
prefix: &str,
name: &str,
value: &T,
)
pub fn write_internally_tagged<T: WriteObject + ?Sized>( &mut self, prefix: &str, name: &str, value: &T, )
Write an internally tagged variant carrying a value:
{"tag":"Name",...}, the payload’s own members following the tag.
prefix is the pre-quoted tag key with its colon and name the
variant’s name, both built at compile time by the macro. The tag is
written first because that is where the reader requires it: this crate
reads in one pass, so a tag after the members it describes could not be
used without a second look at them.
Options::SKIP_NULL reaches the payload’s members, which is what it
means everywhere else, but not the tag: dropping that would leave an
object naming no variant.
Sourcepub fn write_array<T: WriteArray>(&mut self, value: &T)
pub fn write_array<T: WriteArray>(&mut self, value: &T)
Write a struct as a JSON array.
The bracket counterpart of Self::write_object, down to the trailing
comma each element writes and the closing bracket that overwrites the
last of them.
Sourcepub fn element<T: Write + ?Sized>(&mut self, value: &T)
pub fn element<T: Write + ?Sized>(&mut self, value: &T)
Write one value, element.
Options::SKIP_NULL deliberately does not reach here. Dropping a null
from a sequence would shorten it and shift every index after it, which
is a change to the data rather than to its presentation.
Sourcepub fn write_seq_with<'i, A, T, I>(&mut self, items: I)
pub fn write_seq_with<'i, A, T, I>(&mut self, items: I)
Write a sequence as a JSON array, each element through an adapter.
Self::write_seq for elements the adapter describes rather than their
own Write impl. It is what Vec<A>’s WriteAs is built on, and
it is public so that an adapter defined outside this crate can write a
sequence whose elements another adapter describes; the bracket methods
are pub(crate), so write_seq and this are the two ways to a JSON
array. Elements described by neither trait still have to be turned into
something that is.
Sourcepub fn write_keyed<'i, K, V, I>(&mut self, entries: I)
pub fn write_keyed<'i, K, V, I>(&mut self, entries: I)
Write a map as a JSON object.
Keys go through ToJsonKey, so numeric keys come
out quoted, which is the only form JSON has for an object key.
Sourcepub fn write_keyed_with<'i, KA, VA, K, V, I>(&mut self, entries: I)where
KA: WriteKeyAs<K>,
VA: WriteAs<V>,
K: 'i + ?Sized,
V: 'i + ?Sized,
I: IntoIterator<Item = (&'i K, &'i V)>,
pub fn write_keyed_with<'i, KA, VA, K, V, I>(&mut self, entries: I)where
KA: WriteKeyAs<K>,
VA: WriteAs<V>,
K: 'i + ?Sized,
V: 'i + ?Sized,
I: IntoIterator<Item = (&'i K, &'i V)>,
Write a map as a JSON object, keys and values each through an adapter.
Self::write_keyed with both halves adapted, which is what
HashMap<KA, VA>’s WriteAs is built on. Name
Same for a half that wants the type’s own impl.
pub fn write_bool(&mut self, v: bool)
pub fn write_null(&mut self)
pub fn write_u64(&mut self, v: u64)
pub fn write_i64(&mut self, v: i64)
Sourcepub fn write_u128(&mut self, v: u128)
pub fn write_u128(&mut self, v: u128)
Write a u128.
Values past u64::MAX are rare enough that the wide path is a plain
loop rather than a table walk.
Sourcepub fn write_i128_raw(&mut self, v: i128)
pub fn write_i128_raw(&mut self, v: i128)
Write an i128, also used for the quoted-integer key path.
Sourcepub fn write_f64(&mut self, v: f64)
pub fn write_f64(&mut self, v: f64)
Write an f64. NaN and infinity have no JSON form, so they are written
as null, matching Glaze.
pub fn write_f32(&mut self, v: f32)
Sourcepub fn write_number_str(&mut self, s: &str)
pub fn write_number_str(&mut self, s: &str)
Write a number already in its JSON form.
The other half of
Parser::read_number_str,
which is where the case for the pair is written out.
§Panics
Under debug_assertions, if s is not one JSON number literal. That
is a bug in the caller rather than a condition to handle: the document
is already being written, so there is nowhere for an error to go and
nothing to do but publish something no reader will accept. Release
builds append s unchecked, as raw does.
use structio::{Standard, json::Writer};
let mut w = Writer::<Standard>::new();
w.write_number_str("-1.2345678901234567890123e400");
assert_eq!(w.into_string(), "-1.2345678901234567890123e400");Sourcepub fn write_str(&mut self, s: &str)
pub fn write_str(&mut self, s: &str)
Write a quoted, escaped JSON string.
The common case is a string with nothing to escape, and it is copied a
word at a time as it is scanned: each eight bytes are stored into the
spare capacity before the mask says whether they were clean, so a
clean word costs a load, a store and the mask, and the only branch is
the one that is never taken. Scanning first and copying the run
afterwards, as this used to, ended in a memcpy whose length the
compiler could not see, and for the short strings documents are full
of the call cost more than the bytes. The stored word is counted into
the length only as far as the first byte that needs escaping, so an
overshoot lands in spare capacity and is overwritten by what follows.