1
Fork 0

Compare commits

...
Sign in to create a new pull request.

13 commits

14 changed files with 346 additions and 151 deletions

View file

@ -1,65 +0,0 @@
= Changelog
:toc:
:toclevels: 1
:idprefix:
:idseparator: -
== [Unreleased]
== [v1.1.0] - 2024-03-21
=== Added
- implement serializing into generic `io::Write`
=== Fixed
- fix parsing CRLF
== [v1.0.0] - 2023-03-10
=== Added
- implement literal strings
=== Fixed
- fix serializing strings containing `:`
- fix serializing certain escaped characters
== [v0.2.4] - 2023-03-01
=== Fixed
- fix incorrect parsing of unquoted strings
== [v0.2.3] - 2023-02-24
=== Fixed
- support backslashes in delimited strings
== [v0.2.2] - 2023-02-18
=== Fixed
- fix deserialization failing on arrays and objects in some cases
== [v0.2.1] - 2022-12-28
=== Fixed
- fix serializing Unicode
== [v0.2.0] - 2022-11-25
=== Added
* parsing & deserialization
== [v0.1.0] - 2022-11-18
=== Added
* initial release
* serialization

75
CHANGELOG.md Normal file
View file

@ -0,0 +1,75 @@
# Changelog
<!-- next-header -->
## [Unreleased] - ReleaseDate
## [1.2.1] - 2025-04-21
### Changed
- update [nom](https://crates.io/crates/nom) to v8
## [1.2.0] - 2024-03-21
### Added
- publishing to [crates.io](https://crates.io)
## [v1.1.0] - 2024-03-21
### Added
- implement serializing into generic `io::Write`
### Fixed
- fix parsing CRLF
## [v1.0.0] - 2023-03-10
### Added
- implement literal strings
### Fixed
- fix serializing strings containing `:`
- fix serializing certain escaped characters
## [v0.2.4] - 2023-03-01
### Fixed
- fix incorrect parsing of unquoted strings
## [v0.2.3] - 2023-02-24
### Fixed
- support backslashes in delimited strings
## [v0.2.2] - 2023-02-18
### Fixed
- fix deserialization failing on arrays and objects in some cases
## [v0.2.1] - 2022-12-28
### Fixed
- fix serializing Unicode
## [v0.2.0] - 2022-11-25
### Added
* parsing & deserialization
## [v0.1.0] - 2022-11-18
### Added
* initial release
* serialization

View file

@ -1,15 +1,27 @@
[package]
name = "serde_sjson"
version = "1.1.0"
version = "1.2.1"
authors = ["Lucas Schwiderski"]
categories = ["encoding", "parser-implementations"]
description = "An SJSON serialization file format"
documentation = "https://docs.rs/serde_sjson"
edition = "2021"
keywords = ["serde", "serialization", "sjson"]
description = "An SJSON serialization file format"
categories = ["encoding", "parser-implementations"]
license-file = "LICENSE"
repository = "https://github.com/sclu1034/serde_sjson"
exclude = [
".github/",
".ci/",
"Justfile"
]
[dependencies]
nom = "7.1.3"
nom_locate = "4.1.0"
serde = { version = "1.0.154", default-features = false }
nom = "8"
nom_locate = "5"
serde = { version = "1.0", default-features = false }
[dev-dependencies]
serde = { version = "1.0.154", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
[badges]
maintenance = { status = "passively-maintained" }

View file

@ -1,12 +1,33 @@
project := "serde_sjson"
default := "run"
build *ARGS:
cargo build {{ARGS}}
cargo readme > README.md
run *ARGS:
cargo run -- {{ARGS}}
test *ARGS:
cargo test {{ARGS}}
check:
cargo clippy -- -D warnings
cargo test
doc:
cargo doc --no-deps
cargo readme > README.md
serve-doc port='8000': doc
python3 -m http.server {{port}} --directory target/doc
release version execute='': check build doc
git fetch --all
[ "$(git rev-parse master)" = "$(git rev-parse origin/master)" ] \
|| (echo "error: master and origin/master differ" >&2; exit 1)
cargo release --sign --allow-branch master {{ if execute != "" { '-x' } else { '' } }} {{version}}
coverage *ARGS:
RUSTFLAGS="-C instrument-coverage" cargo test --tests {{ARGS}} || true
cargo profdata -- merge -sparse default*.profraw -o {{project}}.profdata

View file

@ -1,13 +0,0 @@
= Serde SJSON
:idprefix:
:idseparator:
:toc: macro
:toclevels: 1
:!toc-title:
:caution-caption: :fire:
:important-caption: :exclamtion:
:note-caption: :paperclip:
:tip-caption: :bulb:
:warning-caption: :warning:
A __ser__ialization/__de__serialization library for __Simplified JSON__, specifically, the Bitsquid/Stingray flavor.

69
README.md Normal file
View file

@ -0,0 +1,69 @@
# serde_sjson
A **ser**ialization/**de**serialization library for Simplified JSON,
the Bitsquid/Stingray flavor of JSON.
## Usage
### Serializing
```rust
use serde::Serialize;
use serde_sjson::Result;
#[derive(Serialize)]
struct Person {
name: String,
age: u8,
friends: Vec<String>,
}
fn main() -> Result<()> {
let data = Person {
name: String::from("Marc"),
age: 21,
friends: vec![String::from("Jessica"), String::from("Paul")],
};
let s = serde_sjson::to_string(&data)?;
println!("{}", s);
Ok(())
}
```
### Deserializing
```rust
use serde::Deserialize;
use serde_sjson::Result;
#[derive(Deserialize)]
struct Person {
name: String,
age: u8,
friends: Vec<String>,
}
fn main() -> Result<()> {
let sjson = r#"
name = Marc
age = 21
friends = [
Jessica
Paul
]"#;
let data: Person = serde_sjson::from_str(sjson)?;
println!(
"{} is {} years old and has {} friends.",
data.name,
data.age,
data.friends.len()
);
Ok(())
}
```

3
README.tpl Normal file
View file

@ -0,0 +1,3 @@
# {{crate}}
{{readme}}

5
release.toml Normal file
View file

@ -0,0 +1,5 @@
pre-release-replacements = [
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}"},
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"},
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [Unreleased] - ReleaseDate", exactly=1},
]

View file

@ -5,6 +5,7 @@ use serde::Deserialize;
use crate::error::{Error, ErrorCode, Result};
use crate::parser::*;
/// A container for deserializing Rust values from SJSON.
pub struct Deserializer<'de> {
input: Span<'de>,
is_top_level: bool,
@ -12,7 +13,7 @@ pub struct Deserializer<'de> {
impl<'de> Deserializer<'de> {
#![allow(clippy::should_implement_trait)]
pub fn from_str(input: &'de str) -> Self {
pub(crate) fn from_str(input: &'de str) -> Self {
Self {
input: Span::from(input),
is_top_level: true,
@ -65,6 +66,8 @@ impl<'de> Deserializer<'de> {
}
}
/// Deserializes an SJSON string to a Rust value.
#[inline]
pub fn from_str<'a, T>(input: &'a str) -> Result<T>
where
T: Deserialize<'a>,
@ -78,7 +81,7 @@ where
}
}
impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut Deserializer<'de> {
impl<'de> serde::de::Deserializer<'de> for &mut Deserializer<'de> {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
@ -418,7 +421,7 @@ impl<'a, 'de: 'a> Separated<'a, 'de> {
}
}
impl<'de, 'a> serde::de::SeqAccess<'de> for Separated<'a, 'de> {
impl<'de> serde::de::SeqAccess<'de> for Separated<'_, 'de> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
@ -440,7 +443,7 @@ impl<'de, 'a> serde::de::SeqAccess<'de> for Separated<'a, 'de> {
}
}
impl<'de, 'a> serde::de::MapAccess<'de> for Separated<'a, 'de> {
impl<'de> serde::de::MapAccess<'de> for Separated<'_, 'de> {
type Error = Error;
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
@ -484,7 +487,7 @@ impl<'a, 'de> Enum<'a, 'de> {
}
}
impl<'de, 'a> EnumAccess<'de> for Enum<'a, 'de> {
impl<'de> EnumAccess<'de> for Enum<'_, 'de> {
type Error = Error;
type Variant = Self;
@ -502,7 +505,7 @@ impl<'de, 'a> EnumAccess<'de> for Enum<'a, 'de> {
}
}
impl<'de, 'a> VariantAccess<'de> for Enum<'a, 'de> {
impl<'de> VariantAccess<'de> for Enum<'_, 'de> {
type Error = Error;
fn unit_variant(self) -> Result<()> {

View file

@ -2,8 +2,11 @@ use std::{fmt, io};
use crate::parser::Token;
/// An alias for a `Result` with `serde_sjson::Error`.
pub type Result<T> = std::result::Result<T, Error>;
/// A type encapsulating the different errors that might occurr
/// during serialization or deserialization.
#[derive(PartialEq)]
pub struct Error {
inner: Box<ErrorImpl>,

View file

@ -1,3 +1,71 @@
//! A **ser**ialization/**de**serialization library for Simplified JSON,
//! the Bitsquid/Stingray flavor of JSON.
//!
//! # Usage
//!
//! ## Serializing
//!
//! ```
//! use serde::Serialize;
//! use serde_sjson::Result;
//!
//! #[derive(Serialize)]
//! struct Person {
//! name: String,
//! age: u8,
//! friends: Vec<String>,
//! }
//!
//! fn main() -> Result<()> {
//! let data = Person {
//! name: String::from("Marc"),
//! age: 21,
//! friends: vec![String::from("Jessica"), String::from("Paul")],
//! };
//!
//! let s = serde_sjson::to_string(&data)?;
//!
//! println!("{}", s);
//!
//! Ok(())
//! }
//! ```
//!
//! ## Deserializing
//!
//! ```
//! use serde::Deserialize;
//! use serde_sjson::Result;
//!
//! #[derive(Deserialize)]
//! struct Person {
//! name: String,
//! age: u8,
//! friends: Vec<String>,
//! }
//!
//! fn main() -> Result<()> {
//! let sjson = r#"
//! name = Marc
//! age = 21
//! friends = [
//! Jessica
//! Paul
//! ]"#;
//!
//! let data: Person = serde_sjson::from_str(sjson)?;
//!
//! println!(
//! "{} is {} years old and has {} friends.",
//! data.name,
//! data.age,
//! data.friends.len()
//! );
//!
//! Ok(())
//! }
//! ```
mod de;
mod error;
mod parser;

View file

@ -4,8 +4,8 @@ use nom::character::complete::{char, digit1, none_of, not_line_ending, one_of};
use nom::combinator::{cut, eof, map, map_res, opt, recognize, value};
use nom::multi::many1_count;
use nom::number::complete::double;
use nom::sequence::{delimited, preceded, terminated, tuple};
use nom::{IResult, Slice};
use nom::sequence::{delimited, preceded, terminated};
use nom::{IResult, Input as _, Parser as _};
use nom_locate::LocatedSpan;
pub(crate) type Span<'a> = LocatedSpan<&'a str>;
@ -35,23 +35,25 @@ fn whitespace(input: Span) -> IResult<Span, char> {
}
fn null(input: Span) -> IResult<Span, ()> {
value((), tag("null"))(input)
value((), tag("null")).parse(input)
}
fn separator(input: Span) -> IResult<Span, &str> {
map(alt((tag(","), tag("\n"), tag("\r\n"))), |val: Span| {
*val.fragment()
})(input)
})
.parse(input)
}
fn bool(input: Span) -> IResult<Span, bool> {
alt((value(true, tag("true")), value(false, tag("false"))))(input)
alt((value(true, tag("true")), value(false, tag("false")))).parse(input)
}
fn integer(input: Span) -> IResult<Span, i64> {
map_res(recognize(tuple((opt(char('-')), digit1))), |val: Span| {
map_res(recognize((opt(char('-')), digit1)), |val: Span| {
val.fragment().parse::<i64>()
})(input)
})
.parse(input)
}
fn float(input: Span) -> IResult<Span, f64> {
@ -61,14 +63,16 @@ fn float(input: Span) -> IResult<Span, f64> {
fn identifier(input: Span) -> IResult<Span, &str> {
map(recognize(many1_count(none_of("\" \t\n=:"))), |val: Span| {
*val.fragment()
})(input)
})
.parse(input)
}
fn literal_string(input: Span) -> IResult<Span, &str> {
map(
delimited(tag("\"\"\""), take_until("\"\"\""), tag("\"\"\"")),
|val: Span| *val.fragment(),
)(input)
)
.parse(input)
}
fn string_content(input: Span) -> IResult<Span, &str> {
@ -84,49 +88,51 @@ fn string_content(input: Span) -> IResult<Span, &str> {
}
'\n' if !escaped => {
let err = nom::error::Error {
input: input.slice(j..),
input: input.take_from(j),
code: nom::error::ErrorKind::Char,
};
return Err(nom::Err::Error(err));
}
'"' if !escaped => {
return Ok((input.slice(j..), &buf[0..j]));
return Ok((input.take_from(j), &buf[0..j]));
}
_ => escaped = false,
}
}
let err = nom::error::Error {
input: input.slice((i + 1)..),
input: input.take_from(i + 1),
code: nom::error::ErrorKind::Char,
};
Err(nom::Err::Failure(err))
}
fn delimited_string(input: Span) -> IResult<Span, &str> {
preceded(char('"'), cut(terminated(string_content, char('"'))))(input)
preceded(char('"'), cut(terminated(string_content, char('"')))).parse(input)
}
fn string(input: Span) -> IResult<Span, &str> {
alt((identifier, literal_string, delimited_string))(input)
alt((identifier, literal_string, delimited_string)).parse(input)
}
fn line_comment(input: Span) -> IResult<Span, &str> {
map(
preceded(tag("//"), alt((not_line_ending, eof))),
|val: Span| *val.fragment(),
)(input)
)
.parse(input)
}
fn block_comment(input: Span) -> IResult<Span, &str> {
map(
delimited(tag("/*"), take_until("*/"), tag("*/")),
|val: Span| *val.fragment(),
)(input)
)
.parse(input)
}
fn comment(input: Span) -> IResult<Span, &str> {
alt((line_comment, block_comment))(input)
alt((line_comment, block_comment)).parse(input)
}
fn optional(input: Span) -> IResult<Span, ()> {
@ -135,7 +141,7 @@ fn optional(input: Span) -> IResult<Span, ()> {
let empty = value((), tag(""));
let content = value((), many1_count(alt((whitespace, comment))));
alt((content, empty))(input)
alt((content, empty)).parse(input)
}
pub(crate) fn parse_next_token(input: Span) -> IResult<Span, Token> {
@ -159,45 +165,48 @@ pub(crate) fn parse_next_token(input: Span) -> IResult<Span, Token> {
map(float, Token::Float),
map(string, |val| Token::String(val.to_string())),
)),
)(input)
)
.parse(input)
}
pub(crate) fn parse_trailing_characters(input: Span) -> IResult<Span, ()> {
value((), optional)(input)
value((), optional).parse(input)
}
pub(crate) fn parse_null(input: Span) -> IResult<Span, Token> {
preceded(optional, value(Token::Null, null))(input)
preceded(optional, value(Token::Null, null)).parse(input)
}
pub(crate) fn parse_separator(input: Span) -> IResult<Span, Token> {
preceded(
opt(horizontal_whitespace),
value(Token::Separator, separator),
)(input)
)
.parse(input)
}
pub(crate) fn parse_bool(input: Span) -> IResult<Span, Token> {
preceded(optional, map(bool, Token::Boolean))(input)
preceded(optional, map(bool, Token::Boolean)).parse(input)
}
pub(crate) fn parse_integer(input: Span) -> IResult<Span, Token> {
preceded(optional, map(integer, Token::Integer))(input)
preceded(optional, map(integer, Token::Integer)).parse(input)
}
pub(crate) fn parse_float(input: Span) -> IResult<Span, Token> {
preceded(optional, map(float, Token::Float))(input)
preceded(optional, map(float, Token::Float)).parse(input)
}
pub(crate) fn parse_identifier(input: Span) -> IResult<Span, Token> {
preceded(
optional,
map(identifier, |val| Token::String(val.to_string())),
)(input)
)
.parse(input)
}
pub(crate) fn parse_string(input: Span) -> IResult<Span, Token> {
preceded(optional, map(string, |val| Token::String(val.to_string())))(input)
preceded(optional, map(string, |val| Token::String(val.to_string()))).parse(input)
}
#[cfg(test)]

View file

@ -7,12 +7,14 @@ use crate::error::{Error, ErrorCode, Result};
// TODO: Make configurable
const INDENT: [u8; 2] = [0x20, 0x20];
/// A container for serializing Rust values into SJSON.
pub struct Serializer<W> {
// The current indentation level
level: usize,
writer: W,
}
/// Serializes a value into a generic `io::Write`.
#[inline]
pub fn to_writer<T, W>(writer: &mut W, value: &T) -> Result<()>
where
@ -23,6 +25,7 @@ where
value.serialize(&mut serializer)
}
/// Serializes a value into a byte vector.
#[inline]
pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
where
@ -33,6 +36,7 @@ where
Ok(vec)
}
/// Serializes a value into a string.
#[inline]
pub fn to_string<T>(value: &T) -> Result<String>
where
@ -51,6 +55,7 @@ impl<W> Serializer<W>
where
W: io::Write,
{
/// Creates a new `Serializer`.
pub fn new(writer: W) -> Self {
Self { level: 0, writer }
}
@ -79,7 +84,7 @@ where
}
}
impl<'a, W> serde::ser::Serializer for &'a mut Serializer<W>
impl<W> serde::ser::Serializer for &mut Serializer<W>
where
W: io::Write,
{
@ -211,9 +216,9 @@ where
self.serialize_unit()
}
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok>
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok>
where
T: serde::Serialize,
T: serde::Serialize + ?Sized,
{
self.ensure_top_level_struct()?;
@ -240,9 +245,9 @@ where
self.serialize_str(variant)
}
fn serialize_newtype_struct<T: ?Sized>(self, _name: &'static str, value: &T) -> Result<Self::Ok>
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Self::Ok>
where
T: serde::Serialize,
T: serde::Serialize + ?Sized,
{
self.ensure_top_level_struct()?;
@ -250,7 +255,7 @@ where
}
// Serialize an externally tagged enum: `{ NAME = VALUE }`.
fn serialize_newtype_variant<T: ?Sized>(
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
@ -258,7 +263,7 @@ where
value: &T,
) -> Result<Self::Ok>
where
T: serde::Serialize,
T: serde::Serialize + ?Sized,
{
self.ensure_top_level_struct()?;
@ -340,24 +345,24 @@ where
Ok(self)
}
fn collect_str<T: ?Sized>(self, value: &T) -> Result<Self::Ok>
fn collect_str<T>(self, value: &T) -> Result<Self::Ok>
where
T: std::fmt::Display,
T: std::fmt::Display + ?Sized,
{
self.serialize_str(&value.to_string())
}
}
impl<'a, W> serde::ser::SerializeSeq for &'a mut Serializer<W>
impl<W> serde::ser::SerializeSeq for &mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
value.serialize(&mut **self)?;
@ -371,16 +376,16 @@ where
}
}
impl<'a, W> serde::ser::SerializeTuple for &'a mut Serializer<W>
impl<W> serde::ser::SerializeTuple for &mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
value.serialize(&mut **self)?;
@ -394,16 +399,16 @@ where
}
}
impl<'a, W> serde::ser::SerializeTupleStruct for &'a mut Serializer<W>
impl<W> serde::ser::SerializeTupleStruct for &mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
value.serialize(&mut **self)?;
@ -417,16 +422,16 @@ where
}
}
impl<'a, W> serde::ser::SerializeTupleVariant for &'a mut Serializer<W>
impl<W> serde::ser::SerializeTupleVariant for &mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
value.serialize(&mut **self)?;
@ -449,24 +454,24 @@ where
}
}
impl<'a, W> serde::ser::SerializeMap for &'a mut Serializer<W>
impl<W> serde::ser::SerializeMap for &mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<()>
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
key.serialize(&mut **self)
}
fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<()>
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
// It doesn't make a difference where the `=` is added. But doing it here
// means `serialize_key` is only a call to a different function, which should
@ -486,16 +491,16 @@ where
}
}
impl<'a, W> serde::ser::SerializeStruct for &'a mut Serializer<W>
impl<W> serde::ser::SerializeStruct for &mut Serializer<W>
where
W: io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()>
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
key.serialize(&mut **self)?;
@ -516,16 +521,16 @@ where
}
}
impl<'a, W> serde::ser::SerializeStructVariant for &'a mut Serializer<W>
impl<W> serde::ser::SerializeStructVariant for &mut Serializer<W>
where
W: std::io::Write,
{
type Ok = ();
type Error = Error;
fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()>
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize,
T: Serialize + ?Sized,
{
self.add_indent()?;
key.serialize(&mut **self)?;

View file

@ -1,4 +1,4 @@
use serde_sjson::{to_string, Error};
use serde_sjson::to_string;
#[test]
fn serialize_null() {