Hello libcode-json

This commit is contained in:
G.H.O.S.T 2024-12-24 21:22:34 +01:00
commit 53255d1e23
Signed by: G.H.O.S.T
GPG Key ID: 3BD93EABD1407B82
51 changed files with 4687 additions and 0 deletions

17
.editorconfig Normal file
View File

@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

View File

@ -0,0 +1,24 @@
name: on-push
on: [push]
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
volumes:
- /build
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Authenticate
run: |
git config unset http.https://code.helloryan.se/.extraheader
echo "${{ secrets.NETRC }}" >> ~/.netrc
- name: Initialize
run: |
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
bdep init -A /build
- name: Build
run: b
- name: Test
run: b test

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
.bdep/
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
README.md Normal file
View File

@ -0,0 +1,21 @@
# libcode-json
![Build status](https://code.helloryan.se/code/libcode-json/actions/workflows/on-push.yaml/badge.svg)
## Requirements
None, other than a modern C++-compiler.
## Building
See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for
build instructions.
## Contact
Please report bugs and issues by sending an e-mail to: ryan@helloryan.se.
## Contributing
Please send an e-mail to ryan@helloryan.se to request an account and
write-access to the libcode-json repository.

4
build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@ -0,0 +1,7 @@
project = libcode-json
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@ -0,0 +1,6 @@
$out_root/
{
include code/json/
}
export $out_root/code/json/$import.target

16
build/root.build Normal file
View File

@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@ -0,0 +1,5 @@
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

9
code/json/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

BIN
code/json/.swp Normal file

Binary file not shown.

65
code/json/buildfile Normal file
View File

@ -0,0 +1,65 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
test_libs = # Test dependencies.
import intf_libs =+ libcode-unicode%lib{code-unicode}
./: lib{code-json}: libul{code-json}
libul{code-json}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{code-json}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} $test_libs
$d/exe{$n}: libul{code-json}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
# Export options.
#
lib{code-json}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{code-json}: bin.lib.version = "-$version.project_id"
else
lib{code-json}: bin.lib.version = "-$version.major.$version.minor"
# Install into the code/json/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/code/json/
install.subdirs = true
}

45
code/json/diagnostics.cxx Normal file
View File

@ -0,0 +1,45 @@
#include <code/json/diagnostics.hxx>
namespace code::json {
std::vector< std::pair< diagnostics::location, std::string > > const&
diagnostics::warnings() const
{
return warnings_;
}
std::vector< std::pair< diagnostics::location, std::string > > const&
diagnostics::errors() const
{
return errors_;
}
void
diagnostics::warning(location loc, std::string description)
{
warnings_.emplace_back(std::make_pair(loc, description));
}
void
diagnostics::error(location loc, std::string description)
{
errors_.emplace_back(std::make_pair(loc, description));
}
std::ostream&
operator<<(std::ostream& o, diagnostics const& d)
{
for (auto const& j : d.warnings()) {
o << "warning: " << j.first.line << ':' << j.first.column << ": "
<< j.second << '\n';
}
for (auto const& j : d.errors()) {
o << "error: " << j.first.line << ':' << j.first.column << ": " << j.second
<< '\n';
}
return o;
}
} // namespace code::json

43
code/json/diagnostics.hxx Normal file
View File

@ -0,0 +1,43 @@
#ifndef code__json__diagnostics_hxx_
#define code__json__diagnostics_hxx_
#include <cstdint>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
namespace code::json {
class diagnostics
{
public:
struct location
{
std::uint32_t line;
std::uint32_t column;
};
std::vector< std::pair< diagnostics::location, std::string > > const&
warnings() const;
std::vector< std::pair< diagnostics::location, std::string > > const&
errors() const;
void
warning(location loc, std::string description);
void
error(location loc, std::string description);
private:
std::vector< std::pair< location, std::string > > warnings_;
std::vector< std::pair< location, std::string > > errors_;
};
std::ostream&
operator<<(std::ostream& o, diagnostics const& d);
} // namespace code::json
#endif

54
code/json/emitter.hxx Normal file
View File

@ -0,0 +1,54 @@
#ifndef code__json__emitter_hxx_
#define code__json__emitter_hxx_
#include <code/json/variant.hxx>
#include <ostream>
#include <stack>
namespace code::json {
class emitter {
public:
explicit emitter(std::ostream& output);
std::ostream&
output();
void
operator()(variant::undefined_t const& value);
void
operator()(bool const& value);
void
operator()(long long int const& value);
void
operator()(unsigned long long int const& value);
void
operator()(long double const& value);
void
operator()(std::string const& value);
void
operator()(std::vector< variant > const& value);
void
operator()(std::map< std::string, variant > const& value);
private:
void
do_indent();
std::ostream& output_;
std::size_t indent_{ 0 };
};
} // namespace code::json
#include <code/json/emitter.ixx>
#endif

141
code/json/emitter.ixx Normal file
View File

@ -0,0 +1,141 @@
namespace code::json {
inline emitter::emitter(std::ostream& output) : output_{ output }
{}
inline std::ostream&
emitter::output()
{
return output_;
}
inline void
emitter::operator()(variant::undefined_t const& value)
{
output() << "null";
}
inline void
emitter::operator()(bool const& value)
{
output() << (value ? "true" : "false");
}
inline void
emitter::operator()(long long int const& value)
{
output() << std::to_string(value);
}
inline void
emitter::operator()(unsigned long long int const& value)
{
output() << std::to_string(value);
}
inline void
emitter::operator()(long double const& value)
{
output() << std::to_string(value);
}
inline void
emitter::operator()(std::string const& value)
{
output() << '\"';
for (auto const j : value) {
switch (j) {
case '\n':
output() << "\\n";
break;
case '\t':
output() << "\\t";
break;
case '\\':
output() << "\\";
break;
case '"':
output() << "\\\"";
break;
// case '\x22':
// case '\x5c':
// case '\x2f':
// case '\x62':
// case '\x66':
// case '\x6e':
// case '\x72':
// case '\x74':
default:
output() << j;
}
}
output() << '\"';
}
inline void
emitter::operator()(std::vector< variant > const& value)
{
if (value.empty()) {
output() << "[]";
return;
}
output() << '[' << '\n';
++indent_;
bool first{ true };
for (auto const& j : value) {
if (!first)
output() << ',' << '\n';
do_indent();
first = false;
visit(*this, j.value_);
}
output() << '\n';
--indent_;
do_indent();
output() << ']';
}
inline void
emitter::operator()(std::map< std::string, variant > const& value)
{
if (value.empty()) {
output() << "{}";
return;
}
output() << "{\n";
++indent_;
bool first{ true };
for (auto const& j : value) {
if (!first)
output() << ",\n";
first = false;
do_indent();
(*this)(j.first);
output() << ':' << ' ';
visit(*this, j.second.value_);
}
output() << '\n';
--indent_;
do_indent();
output() << '}';
}
inline void
emitter::do_indent()
{
std::size_t i{ indent_ * 2 };
while (i-- > 0)
output() << ' ';
}
} // namespace code::json

115
code/json/emitter.test.cxx Normal file
View File

@ -0,0 +1,115 @@
#include <code/json/emitter.hxx>
#include <iostream>
#include <sstream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("undefined")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(code::json::variant::undefined_t{});
TEST_EQUAL(str.str(), "null");
}
DEFINE_TEST("boolean: true")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(true);
TEST_EQUAL(str.str(), "true");
}
DEFINE_TEST("boolean: false")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(false);
TEST_EQUAL(str.str(), "false");
}
DEFINE_TEST("signed number")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(-10LL);
TEST_EQUAL(str.str(), "-10");
}
DEFINE_TEST("unsigned number")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(100LL);
TEST_EQUAL(str.str(), "100");
}
DEFINE_TEST("real number")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(1.2L);
TEST_EQUAL(str.str(), "1.2");
}
DEFINE_TEST("string")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(std::string{ "hello" });
TEST_EQUAL(str.str(), "\"hello\"");
}
DEFINE_TEST("array: []")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(std::vector< code::json::variant >{});
TEST_EQUAL(str.str(), "[]");
}
DEFINE_TEST("array: [null]")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(std::vector< code::json::variant >{ code::json::variant{} });
TEST_EQUAL(str.str(), "[\n null\n]");
}
DEFINE_TEST("object: {}")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(std::map< std::string, code::json::variant >{});
TEST_EQUAL(str.str(), "{}");
}
DEFINE_TEST("object: {...}")
{
std::stringstream str;
code::json::emitter emitter{ str };
emitter(std::map< std::string, code::json::variant >{ { "", {} } });
TEST_EQUAL(str.str(), "{\n \"\": null\n}");
}
return 0;
}

43
code/json/exception.cxx Normal file
View File

@ -0,0 +1,43 @@
#include <code/json/exception.hxx>
namespace code::json {
invalid_json::invalid_json(diagnostics::location const& loc,
std::string const& what)
: std::runtime_error{ make_error_string(loc, what) }, location_{ loc }
{}
diagnostics::location const&
invalid_json::location() const
{
return location_;
}
std::string
invalid_json::make_error_string(diagnostics::location const& loc,
std::string const& what)
{
std::ostringstream str;
str << loc.column << " " << loc.line << ": " << what;
return str.str();
}
invalid_type::invalid_type() : std::runtime_error{ "invalid type" }
{}
invalid_syntax::invalid_syntax() : std::runtime_error{ "invalid syntax" }
{}
unexpected_type::unexpected_type()
: std::runtime_error{ "unexpected JSON type" }
{}
invalid_object_key::invalid_object_key(std::string const& key)
: std::runtime_error{ "invalid key '" + key + "'" }
{}
invalid_array_index::invalid_array_index(std::string const& index)
: std::runtime_error{ "invalid key '" + index + "'" }
{}
} // namespace code::json

59
code/json/exception.hxx Normal file
View File

@ -0,0 +1,59 @@
#ifndef code__json__exception_hxx_
#define code__json__exception_hxx_
#include <code/json/diagnostics.hxx>
#include <sstream>
#include <stdexcept>
#include <string>
namespace code::json {
/// Exception-class used to indicated invalid JSON.
class invalid_json : public std::runtime_error {
public:
invalid_json(diagnostics::location const& loc, std::string const& what);
diagnostics::location const&
location() const;
static std::string
make_error_string(diagnostics::location const& loc, std::string const& what);
private:
diagnostics::location location_;
};
/// Exception-class used to indicate an invalid type.
class invalid_type : public std::runtime_error {
public:
invalid_type();
};
/// Exception class used to indicate invalid JSON pointer syntax.
class invalid_syntax : public std::runtime_error {
public:
invalid_syntax();
};
/// Exception class used to indicate an unexpected JSON type.
class unexpected_type : public std::runtime_error {
public:
unexpected_type();
};
/// Exception-class used to indicate an object key.
class invalid_object_key : public std::runtime_error {
public:
explicit invalid_object_key(std::string const& key);
};
/// Exception-class used to indicate an invalid array index.
class invalid_array_index : public std::runtime_error {
public:
explicit invalid_array_index(std::string const& index);
};
} // namespace code::json
#endif

View File

@ -0,0 +1,245 @@
#ifndef json__marshaling__mapping_hxx_
#define json__marshaling__mapping_hxx_
#include <code/json/optional.hxx>
#include <code/json/pointer.hxx>
#include <code/json/variant.hxx>
#include <code/json/marshaling/marshaling-traits.hxx>
#include <code/json/marshaling/traits.hxx>
#include <functional>
#include <variant>
namespace code::json::marshaling {
template< typename T >
class member_mapping {
public:
using getter_type = std::function< variant(T const&, marshaling_context*) >;
using setter_type = std::function< void(T&, variant const&, marshaling_context*) >;
member_mapping(std::string key,
bool optional,
getter_type getter,
setter_type setter)
: key_{ std::move(key) },
optional_{ optional },
getter_{ std::move(getter) },
setter_{ std::move(setter) }
{}
member_mapping(pointer p,
bool optional,
getter_type getter,
setter_type setter)
: key_{ std::move(p) },
optional_{ optional },
getter_{ std::move(getter) },
setter_{ std::move(setter) }
{}
template< typename U >
member_mapping(member_mapping< U > const& other)
: key_{other.key_},
optional_{other.optional_},
getter_{other.getter_},
setter_{other.setter_}
{}
std::variant<std::string, pointer> const&
key() const
{
return key_;
}
bool
optional() const
{
return optional_;
}
variant
get(T const& instance, marshaling_context* context) const
{
return getter_(instance, context);
}
void
set(T& instance, variant const& value, marshaling_context* context) const
{
setter_(instance, value, context);
}
private:
template< typename U >
friend class member_mapping;
std::variant<std::string, pointer> key_;
bool optional_;
getter_type getter_;
setter_type setter_;
};
template< typename T, typename M >
member_mapping< T >
member(std::string key, M T::*member)
{
return member_mapping< T >{
std::move(key),
is_optional_v<M>,
[member](T const& instance, marshaling_context* context) -> json::variant
{
return marshaling_traits< M >::marshal(instance.*member, context);
},
[member](T& instance, json::variant const& v, marshaling_context* context)
{
instance.*member = marshaling_traits< M >::unmarshal(v, context);
}
};
}
template< typename T, typename M >
member_mapping< T >
member(pointer p, M T::*member)
{
return member_mapping< T >{
std::move(p),
is_optional_v<M>,
[member](T const& instance, marshaling_context* context) -> json::variant
{
return marshaling_traits< M >::marshal(instance.*member, context);
},
[member](T& instance, json::variant const& v, marshaling_context* context)
{
instance.*member = marshaling_traits< M >::unmarshal(v, context);
}
};
}
template< typename T, typename Accessor >
member_mapping< T >
accessor(std::string key, Accessor access)
{
return member_mapping< T >{
std::move(key),
is_optional_v<std::decay_t<decltype(access(std::declval<T>()))>>,
[access](T const& instance, marshaling_context* context) -> json::variant
{
using M = std::decay_t<decltype(access(instance))>;
return marshaling_traits< M >::marshal(access(instance), context);
},
[access](T& instance, json::variant const& v, marshaling_context* context)
{
using M = std::decay_t<decltype(access(instance))>;
access(instance) = marshaling_traits< M >::unmarshal(v, context);
}
};
}
template< typename T, typename Accessor >
member_mapping< T >
accessor(pointer p, Accessor access)
{
return member_mapping< T >{
std::move(p),
is_optional_v<std::decay_t<decltype(access(std::declval<T>()))>>,
[access](T const& instance, marshaling_context* context) -> json::variant
{
using M = std::decay_t<decltype(access(instance))>;
return marshaling_traits< M >::marshal(access(instance), context);
},
[access](T& instance, json::variant const& v, marshaling_context* context)
{
using M = std::decay_t<decltype(access(instance))>;
access(instance) = marshaling_traits< M >::unmarshal(v, context);
}
};
}
template< typename T, typename V >
member_mapping< T >
static_value(std::string key, V value)
{
return member_mapping< T >{
std::move(key),
true,
[value](T const& instance, marshaling_context* context) -> json::variant {
return marshaling_traits< V >::marshal(value, context);
},
[](T&, json::variant const&, marshaling_context*) {
// No-op.
}
};
}
template< typename T, typename V >
member_mapping< T >
static_value(pointer p, V value)
{
return member_mapping< T >{
std::move(p),
true,
[value](T const& instance, marshaling_context* context) -> json::variant {
return marshaling_traits< V >::marshal(value, context);
},
[](T&, json::variant const&, marshaling_context*) {
// No-op.
}
};
}
template< typename T >
class mapping {
public:
using container_type = std::vector< member_mapping< T > >;
using const_iterator = typename container_type::const_iterator;
mapping(container_type mappings)
: mappings_{std::move(mappings)}
{}
mapping(std::initializer_list< member_mapping< T > > init)
{
for (auto const& j : init)
mappings_.emplace_back(j);
}
const_iterator
begin() const
{
return mappings_.begin();
}
const_iterator
end() const
{
return mappings_.end();
}
private:
container_type mappings_;
};
template<typename T, typename... Bases>
mapping<T>
map(std::initializer_list<member_mapping<T>> init)
{
typename mapping<T>::container_type m{init};
auto inherit = [&](auto&& base)
{
for (auto const& j : base) {
m.emplace_back(j);
}
};
((inherit(Bases::json())), ...);
return m;
}
} // namespace code::json::marshaling
#endif

View File

@ -0,0 +1,8 @@
#include <code/json/marshaling/marshaling-context.hxx>
namespace code::json::marshaling {
marshaling_context::~marshaling_context()
{}
} // namespace code::json::marshaling

View File

@ -0,0 +1,13 @@
#ifndef code__json__marshaling__marshaling_context_hxx_
#define code__json__marshaling__marshaling_context_hxx_
namespace code::json::marshaling {
class marshaling_context {
protected:
virtual ~marshaling_context();
};
} // namespace code::json::marshaling
#endif

View File

@ -0,0 +1,626 @@
#ifndef code__json__marshaling__marshaling_traits_hxx_
#define code__json__marshaling__marshaling_traits_hxx_
#include <code/json/optional.hxx>
#include <code/json/pointer.hxx>
#include <code/json/variant.hxx>
#include <code/json/marshaling/marshaling-context.hxx>
#include <chrono>
#include <ctime>
#include <deque>
#include <iomanip>
#include <iostream> // TODO: Remove.
#include <list>
#include <locale>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
#include <time.h>
namespace code::json::marshaling {
template< typename T >
struct marshaling_traits {
using model_type = T;
static variant
marshal(model_type const& model, marshaling_context* context)
{
variant v{std::map<std::string, variant>{}};
// TODO: Handle mappings with pointers.
for (auto const& mapping : model_type::json()) {
if (std::holds_alternative<pointer>(mapping.key())) {
auto ptr = std::get<pointer>(mapping.key());
ptr.write(v, mapping.get(model, context));
}
else {
v.set(std::get<std::string>(mapping.key()), mapping.get(model, context));
}
}
return v;
}
static model_type
unmarshal(variant const& value, marshaling_context* context)
{
if constexpr (std::is_default_constructible_v< model_type >) {
// TODO: Change exception type.
if (!value.is_object())
throw std::runtime_error{ "cannot unmarshal non-object value" };
model_type model;
for (auto const& mapping : model_type::json()) {
if (std::holds_alternative<pointer>(mapping.key())) {
auto ptr = std::get<pointer>(mapping.key());
auto v = ptr.read(value);
if (!v && !mapping.optional())
// FIXME: Add ptr to exception.
throw std::runtime_error{ "missing field '" + to_string(ptr) + "'" };
else
mapping.set(model, *v, context);
continue;
}
auto const& key = std::get<std::string>(mapping.key());
if (!value.contains(key)) {
if (!mapping.optional())
// TODO: Change exception type.
throw std::runtime_error{ "missing field '" + key + "'" };
continue; // Try next key.
}
mapping.set(model, value.get(key), context);
}
return model;
}
else {
throw std::runtime_error{ "this shouldn't compile" };
}
}
};
template< typename T >
struct marshaling_traits< optional< T > > {
using model_type = optional< T >;
static variant
marshal(optional< T > const& model, marshaling_context* context)
{
if (model)
return marshaling_traits< T >::marshal(*model, context);
return {};
}
static optional< T >
unmarshal(variant const& value, marshaling_context* context)
{
if (value.is_undefined())
return {};
return marshaling_traits< T >::unmarshal(value, context);
}
};
template< typename... T >
struct marshaling_traits< std::variant< T... > > {
using model_type = std::variant< T... >;
static variant
marshal(std::variant< T... > const& model, marshaling_context* context)
{
variant result;
auto visitor = [&result, context](const auto& obj)
{
using type = std::decay_t<decltype(obj)>;
result = marshaling_traits<type>::marshal(obj, context);
result.set("$type", std::decay_t<decltype(obj)>::type_identifier);
};
std::visit(visitor, model);
return result;
}
template<typename Current, typename... Next>
struct unmarshaler
{
static
std::variant<T...>
unmarshal(variant const& value, marshaling_context* context)
{
if (!value.contains("$type"))
throw std::invalid_argument{"variant missing '$type' identifier"};
if (value.get("$type").get_string() == Current::type_identifier)
return marshaling_traits< Current >::unmarshal(value, context);
if constexpr (sizeof...(Next) > 0)
return unmarshaler<Next...>::unmarshal(value, context);
throw std::invalid_argument{"couldn't unmarshal value"};
}
};
static std::variant< T... >
unmarshal(variant const& value, marshaling_context* context)
{
return unmarshaler<T...>::unmarshal(value, context);
}
};
template<>
struct marshaling_traits< bool > {
using model_type = bool;
static variant
marshal(bool model, marshaling_context*)
{
return model;
}
static bool
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_boolean())
throw std::runtime_error{ "not a boolean" };
return value.get_boolean();
}
};
template<>
struct marshaling_traits< short int > {
using model_type = short int;
static variant
marshal(short int model, marshaling_context*)
{
return model;
}
static short int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< short int >();
}
};
template<>
struct marshaling_traits< int > {
using model_type = int;
static variant
marshal(int model, marshaling_context*)
{
return model;
}
static int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< int >();
}
};
template<>
struct marshaling_traits< long int > {
using model_type = long int;
static variant
marshal(long int model, marshaling_context*)
{
return model;
}
static long int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< long int >();
}
};
template<>
struct marshaling_traits< long long int > {
using model_type = long long int;
static variant
marshal(long long int model, marshaling_context*)
{
return model;
}
static long long int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< long long int >();
}
};
template<>
struct marshaling_traits< unsigned short int > {
using model_type = unsigned short int;
static variant
marshal(unsigned short int model, marshaling_context*)
{
return model;
}
static unsigned short int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< unsigned short int >();
}
};
template<>
struct marshaling_traits< unsigned int > {
using model_type = unsigned int;
static variant
marshal(unsigned int model, marshaling_context*)
{
return model;
}
static unsigned int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< unsigned int >();
}
};
template<>
struct marshaling_traits< unsigned long int > {
using model_type = unsigned long int;
static variant
marshal(unsigned long int model, marshaling_context*)
{
return model;
}
static unsigned long int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< unsigned long int >();
}
};
// unsigned long long int
template<>
struct marshaling_traits< unsigned long long int > {
using model_type = unsigned long long int;
static variant
marshal(unsigned long long int model, marshaling_context*)
{
return model;
}
static unsigned long long int
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< unsigned long long int >();
}
};
template<>
struct marshaling_traits< float > {
using model_type = float;
static variant
marshal(float model, marshaling_context*)
{
return model;
}
static float
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< float >();
}
};
template<>
struct marshaling_traits< double > {
using model_type = double;
static variant
marshal(double model, marshaling_context*)
{
return model;
}
static double
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< double >();
}
};
template<>
struct marshaling_traits< long double > {
using model_type = long double;
static variant
marshal(long double model, marshaling_context*)
{
return model;
}
static long double
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_number())
throw std::runtime_error{ "not a number" };
return value.get_number< long double >();
}
};
template<>
struct marshaling_traits< std::string > {
using model_type = std::string;
static variant
marshal(std::string const& model, marshaling_context*)
{
return model;
}
static std::string
unmarshal(variant const& value, marshaling_context*)
{
if (!value.is_string())
throw std::runtime_error{ "not a string" };
return value.get_string();
}
};
template< typename T >
struct marshaling_traits< std::vector< T > > {
using model_type = std::vector< T >;
static variant
marshal(std::vector< T > const& model, marshaling_context* context)
{
std::vector< variant > a;
for (auto const& j : model)
a.emplace_back(marshaling_traits< T >::marshal(j, context));
return a;
}
static std::vector< T >
unmarshal(variant const& value, marshaling_context* context)
{
if (!value.is_array())
throw std::runtime_error{ "not an array" };
std::vector< T > model;
for (auto const& j : value)
model.emplace_back(marshaling_traits< T >::unmarshal(j, context));
return model;
}
};
template< typename T >
struct marshaling_traits< std::list< T > > {
using model_type = std::list< T >;
static variant
marshal(std::list< T > const& model, marshaling_context* context)
{
std::vector< variant > a;
for (auto const& j : model)
a.emplace_back(marshaling_traits< T >::marshal(j, context));
return a;
}
static std::list< T >
unmarshal(variant const& value, marshaling_context* context)
{
if (!value.is_array())
throw std::runtime_error{ "not an array" };
std::list< T > model;
for (auto const& j : value)
model.emplace_back(marshaling_traits< T >::unmarshal(j, context));
return model;
}
};
template< typename T >
struct marshaling_traits< std::deque< T > > {
using model_type = std::deque< T >;
static variant
marshal(std::deque< T > const& model, marshaling_context* context)
{
std::vector< variant > a;
for (auto const& j : model)
a.emplace_back(marshaling_traits< T >::marshal(j, context));
return a;
}
static std::deque< T >
unmarshal(variant const& value, marshaling_context* context)
{
if (!value.is_array())
throw std::runtime_error{ "not an array" };
std::deque< T > model;
for (auto const& j : value)
model.emplace_back(marshaling_traits< T >::unmarshal(j, context));
return model;
}
};
template< typename T >
struct marshaling_traits< std::set< T > > {
using model_type = std::set< T >;
static variant
marshal(std::set< T > const& model, marshaling_context* context)
{
std::vector< variant > a;
for (auto const& j : model)
a.emplace_back(marshaling_traits< T >::marshal(j, context));
return a;
}
static std::set< T >
unmarshal(variant const& value, marshaling_context* context)
{
if (!value.is_array())
throw std::runtime_error{ "not an array" };
std::set< T > model;
for (auto const& j : value)
model.emplace(marshaling_traits< T >::unmarshal(j, context));
return model;
}
};
template<>
struct marshaling_traits< std::chrono::system_clock::time_point > {
using model_type = std::string;
static variant
marshal(std::chrono::system_clock::time_point const& model,
marshaling_context*)
{
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
std::time_t now_c = std::chrono::system_clock::to_time_t(model);
struct std::tm tm_buf;
std::stringstream str;
str.imbue(std::locale{});
#ifdef _MSC_VER
::gmtime_s(&tm_buf, &now_c); // Stupid Microsoft.
str << std::put_time(&tm_buf, time_format);
#else
::gmtime_r(&now_c, &tm_buf);
str << std::put_time(&tm_buf, time_format);
#endif
return str.str();
}
static std::chrono::system_clock::time_point
unmarshal(variant const& value, marshaling_context*)
{
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
if (!value.is_string())
throw std::runtime_error{ "not a string" };
std::tm tm{};
std::istringstream str{ value.get_string() };
str.imbue(std::locale{});
str >> std::get_time(&tm, time_format);
if (str.fail())
return {};
#ifdef _MSC_VER
auto localtime = _mkgmtime(&tm);
return std::chrono::system_clock::from_time_t(localtime);
#else
auto localtime = timegm(&tm);
return std::chrono::system_clock::from_time_t(localtime);
#endif
}
};
template<typename E,
std::string(&to_string)(E const&),
E(&from_string)(std::string const&)>
struct enum_mapping
{
static
variant
marshal(E const& value, marshaling_context*)
{
return to_string(value);
}
static
E
unmarshal(variant const& v, marshaling_context*)
{
return from_string(v.get_string());
}
};
} // namespace code::json::marshaling
#endif

View File

@ -0,0 +1,146 @@
#include <code/json/optional.hxx>
#include <code/json/marshaling/marshaling-traits.hxx>
#include <cstdint>
#include <deque>
#include <iostream>
#include <list>
#include <string>
#include <vector>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_FALSE(x) if ((x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("optional<int>{}")
{
using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >;
std::optional< int > model;
auto j = traits_type::marshal(model, nullptr);
TEST_TRUE(j.is_undefined());
}
DEFINE_TEST("optional<int>{0}")
{
using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >;
std::optional< int > model{ 0 };
auto j = traits_type::marshal(model, nullptr);
TEST_FALSE(j.is_undefined());
TEST_TRUE(j.is_number());
}
DEFINE_TEST("string")
{
using traits_type = code::json::marshaling::marshaling_traits< std::string >;
std::string model{ "hello, world" };
auto j = traits_type::marshal(model, nullptr);
TEST_TRUE(j.is_string());
TEST_EQUAL(j.get_string(), "hello, world");
}
DEFINE_TEST("vector<int>{}")
{
using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >;
std::vector< int > model1;
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("vector<int>{...}")
{
using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >;
std::vector< int > model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("list<int>{}")
{
using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >;
std::list< int > model1;
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("list<int>{...}")
{
using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >;
std::list< int > model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("deque<int>{}")
{
using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >;
std::deque< int > model1;
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
DEFINE_TEST("deque<int>{...}")
{
using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >;
std::deque< int > model1{ 1, 2, 3, 4 };
auto j = traits_type::marshal(model1, nullptr);
TEST_TRUE(j.is_array());
auto model2 = traits_type::unmarshal(j, nullptr);
TEST_EQUAL(model1, model2);
}
return 0;
}

View File

@ -0,0 +1,29 @@
#ifndef code__json__marshaling__marshaling_hxx_
#define code__json__marshaling__marshaling_hxx_
#include <code/json/variant.hxx>
#include <code/json/marshaling/marshaling-context.hxx>
#include <code/json/marshaling/marshaling-traits.hxx>
#include <utility>
namespace code::json::marshaling {
template< typename T >
variant
marshal(T const& model, marshaling_context* context = nullptr)
{
return marshaling_traits< T >::marshal(model, context);
}
template< typename T >
typename marshaling_traits< T >::model_type
unmarshal(variant const& value, marshaling_context* context = nullptr)
{
return marshaling_traits< T >::unmarshal(value, context);
}
} // namespace code::json::marshaling
#endif

View File

@ -0,0 +1,91 @@
#ifndef code__json__marshaling__serialize_hxx_
#define code__json__marshaling__serialize_hxx_
#include <code/json/marshaling/marshaling-context.hxx>
#include <code/json/marshaling/marshaling.hxx>
#include <code/json/read.hxx>
#include <code/json/write.hxx>
#include <istream>
namespace code::json::marshaling {
template< typename T >
void
serialize(std::ostream& o, T const& model)
{
write(o, marshal(model));
}
template< typename T >
void
serialize(std::ostream&& o, T const& model)
{
serialize(o, model);
}
template< typename T >
std::string
serialize(T const& model)
{
std::ostringstream str;
serialize(str, model);
return str.str();
}
template< typename T >
T
deserialize(diagnostics& d,
std::istream& i,
marshaling_context* context = nullptr)
{
return unmarshal< T >(read(d, i), context);
}
template< typename T >
T
deserialize(std::istream& i, marshaling_context* context = nullptr)
{
return unmarshal< T >(read(i), context);
}
template< typename T >
T
deserialize(diagnostics& d,
std::istream&& i,
marshaling_context* context = nullptr)
{
return deserialize< T >(d, i, context);
}
template< typename T >
T
deserialize(std::istream&& i, marshaling_context* context = nullptr)
{
return deserialize< T >(i, context);
}
template< typename T >
T
deserialize(diagnostics& d,
std::string const& str,
marshaling_context* context = nullptr)
{
// TODO use std::string overload of read
return deserialize< T >(
d, std::istringstream{ str, std::ios::in | std::ios::binary }, context);
}
template< typename T >
T
deserialize(std::string const& str, marshaling_context* context = nullptr)
{
// TODO use std::string overload of read
return deserialize< T >(
std::istringstream{ str, std::ios::in | std::ios::binary }, context);
}
} // namespace code::json::marshaling
#endif

View File

@ -0,0 +1,60 @@
#include <code/json/marshaling/mapping.hxx>
#include <code/json/marshaling/serialize.hxx>
#include <iostream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_FALSE(x) if ((x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
struct person_name {
std::string first;
std::string last;
static code::json::marshaling::mapping< person_name > const&
json()
{
static code::json::marshaling::mapping< person_name > const mapping{
code::json::marshaling::member("first", &person_name::first),
code::json::marshaling::member("last", &person_name::last)
};
return mapping;
}
};
struct person {
person_name name;
int age;
static code::json::marshaling::mapping< person > const&
json()
{
static code::json::marshaling::mapping< person > const mapping{
code::json::marshaling::member("person", &person::name),
code::json::marshaling::member("age", &person::age)
};
return mapping;
}
};
int
main()
{
DEFINE_TEST("round trip")
{
using namespace code::json::marshaling;
person p1{ { "Jane", "Doe" }, 37 };
auto p2 = deserialize< person >(serialize(p1));
TEST_EQUAL(p1.name.first, p2.name.first);
TEST_EQUAL(p1.name.last, p2.name.last);
TEST_EQUAL(p1.age, p2.age);
}
return 0;
}

View File

@ -0,0 +1,72 @@
#ifndef code__json__marshaling__traits_hxx_
#define code__json__marshaling__traits_hxx_
#include <code/json/optional.hxx>
#include <type_traits>
namespace code::json::marshaling {
// is_optional<T>.
//
template< typename >
struct is_optional : std::false_type {
};
template< typename T >
struct is_optional< optional< T > > : std::true_type {
};
template< typename T >
constexpr bool is_optional_v = is_optional< T >::value;
// function_traits<T>.
//
template< typename, typename = std::void_t< > >
struct function_traits;
template< typename Ret, typename... Args >
struct function_traits< Ret(Args...) > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Ret, typename Class, typename... Args >
struct function_traits< Ret (Class::*)(Args...) > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using class_type = Class;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Ret, typename Class, typename... Args >
struct function_traits< Ret (Class::*)(Args...) const > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using class_type = Class;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Ret, typename... Args >
struct function_traits< Ret(*)(Args...) > {
static constexpr std::size_t arity = sizeof...(Args);
using return_type = std::decay_t< Ret >;
using argument_tuple = std::tuple< std::decay_t< Args >... >;
};
template< typename Callable >
struct function_traits<
Callable,
std::void_t<
decltype(&Callable::operator())
>
>
: function_traits<decltype(&Callable::operator())> {
};
} // namespace code::json::marshaling
#endif

30
code/json/optional.hxx Normal file
View File

@ -0,0 +1,30 @@
#ifndef code__json__optional_hxx_
#define code__json__optional_hxx_
/*
* This file will be REMOVED once <optional> is widely available.
*/
#if !defined(__has_include)
# error "__has_include not supported"
#endif
#if __has_include(<optional>)
# include <optional>
#elif __has_include(<experimental/optional>)
# include <experimental/optional>
#else
# error "No support for <optional>"
#endif
namespace code::json {
#if __has_include(<optional>)
using std::optional;
#elif __has_include(<experimental/optional>)
using std::experimental::optional;
#endif
} // namespace code::json
#endif

758
code/json/parser.hxx Normal file
View File

@ -0,0 +1,758 @@
#ifndef code__json__parser_hxx_
#define code__json__parser_hxx_
#include <code/json/diagnostics.hxx>
#include <code/json/exception.hxx>
#include <code/json/optional.hxx>
#include <code/json/variant.hxx>
#include <code/unicode/decoding.hxx>
#include <code/unicode/encoding.hxx>
#include <iostream> // TODO remove
#include <sstream>
#include <string>
#include <limits>
namespace code::json {
class variant;
class parser {
public:
parser() = default;
template< typename InputIterator >
optional< variant >
try_parse(diagnostics& d, InputIterator& first, InputIterator last)
{
auto v = parse(d, first, last);
if (d.errors().size() > 0)
return variant::undefined;
return v;
}
template< typename InputIterator >
optional< variant >
try_parse(diagnostics& d, InputIterator&& first, InputIterator last)
{
return try_parse(d, first, last);
}
template< typename InputIterator >
variant
parse(diagnostics& d, InputIterator& first, InputIterator last)
{
skip_whitespace(first, last);
auto v = try_parse_any(d, first, last);
if (d.errors().size() > 0)
return variant::undefined;
skip_whitespace(first, last);
if (first != last) {
d.error(location(), "unexpected trailing data");
return variant::undefined;
}
return v;
}
template< typename InputIterator >
variant
parse(diagnostics& d, InputIterator&& first, InputIterator last)
{
return parse(d, first, last);
}
private:
template< typename InputIterator >
variant
try_parse_undefined(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i != 'n') {
d.error(location(), "expected 'n'");
return { loc, variant::undefined };
}
advance(i, last);
if (i == last || *i != 'u') {
d.error(location(), "expected 'u'");
return { loc, variant::undefined };
}
advance(i, last);
if (i == last || *i != 'l') {
d.error(location(), "expected 'l'");
return { loc, variant::undefined };
}
advance(i, last);
if (i == last || *i != 'l') {
d.error(location(), "expected 'l'");
return { loc, variant::undefined };
}
advance(i, last);
return { std::move(loc), variant::undefined };
}
template< typename InputIterator >
variant
try_parse_array(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i != '[') {
d.error(location(), "expected '['");
return variant::undefined;
}
advance(i, last); // skip '['
skip_whitespace(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i == ']') {
advance(i, last);
return variant{ std::move(loc), std::vector< variant >{} };
}
std::vector< variant > a;
for (;;) {
auto v = try_parse_any(d, i, last);
if (d.errors().size() > 0)
return variant::undefined;
a.emplace_back(std::move(v));
skip_whitespace(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (',' != *i)
break;
advance(i, last);
skip_whitespace(i, last);
}
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != ']') {
d.error(location(), "expected ']'");
return variant::undefined;
}
advance(i, last);
return variant{ std::move(loc), std::move(a) };
}
template< typename InputIterator >
variant
try_parse_boolean(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i == 't') {
advance(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'r') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip 'r'
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'u') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip 'u'
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'e') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip 'e'
return variant{ std::move(loc), true };
}
if (*i != 'f')
return {};
advance(i, last);
if (i == last || *i != 'a')
return {};
advance(i, last);
if (i == last || *i != 'l')
return {};
advance(i, last);
if (i == last || *i != 's')
return {};
advance(i, last);
if (i == last || *i != 'e')
return {};
advance(i, last);
return variant{ std::move(loc), false };
}
template< typename InputIterator >
variant
try_parse_number(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
std::string buf;
buf.reserve(std::numeric_limits< long long >::digits10 * 2);
bool is_signed{ false };
if (i != last && *i == '-') {
buf.push_back(*i++);
is_signed = true;
}
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
// int
if (*i == '0') {
buf.push_back(*i++);
}
else if ('1' <= *i && *i <= '9') {
do {
buf.push_back(*i++);
} while (i != last && '0' <= *i && *i <= '9');
}
else {
d.error(location(), "unexpected character");
return variant::undefined;
}
bool is_real{ false };
// frac
if (i != last && *i == '.') {
is_real = true;
buf.push_back(*i++);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i < '0' || '9' < *i) {
d.error(location(), "unexpected character");
return variant::undefined;
}
do {
buf.push_back(*i++);
} while (i != last && '0' <= *i && *i <= '9');
}
// exp
if (i != last && (*i == 'e' || *i == 'E')) {
is_real = true;
buf.push_back(*i++);
if (i != last && (*i == '-' || *i == '+'))
buf.push_back(*i++);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i < '0' || '9' < *i) {
d.error(location(), "unexpected character");
return variant::undefined;
}
do {
buf.push_back(*i++);
} while (i != last && '0' <= *i && *i <= '9');
}
try {
if (is_real) {
return variant{ loc, std::stold(buf) };
}
if (is_signed) {
return variant{ loc, std::stoll(buf) };
}
return variant{ loc, std::stoull(buf) };
}
catch (std::out_of_range const&) {
d.error(loc, "number too large");
}
return variant::undefined;
}
template< typename InputIterator >
variant
try_parse_object(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (*i != '{')
return {};
advance(i, last);
skip_whitespace(i, last);
if (i == last)
return {};
if (*i == '}') {
advance(i, last);
return std::map< std::string, variant >{};
}
std::map< std::string, variant > o;
for (;;) {
auto k = try_parse_string(d, i, last);
if (d.errors().size() > 0)
return {};
skip_whitespace(i, last);
if (i == last || *i != ':')
return {};
advance(i, last);
skip_whitespace(i, last);
if (i == last) {
d.error(location(), "unexpected end");
return {};
}
auto v = try_parse_any(d, i, last);
if (d.errors().size() > 0)
return {};
variant v_v{ std::move(v) };
o.emplace(k.get_string(), std::move(v_v));
skip_whitespace(i, last);
if (i == last)
return {};
if (',' != *i)
break;
advance(i, last);
skip_whitespace(i, last);
}
if (i == last || *i != '}')
return {};
advance(i, last);
return variant{ std::move(loc), std::move(o) };
}
template< typename InputIterator >
variant
try_parse_string(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
std::string text;
if (*i != '"') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last);
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
while (i != last && *i != '"') {
if (*i == '\\') { // Parse escape sequence.
advance(i, last); // skip '\'
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
switch (*i) {
case '"':
text += '\x22';
advance(i, last);
break;
case '\\':
text += '\x5c';
advance(i, last);
break;
case '/':
text += '\x2f';
advance(i, last);
break;
case 'b':
text += '\x62';
advance(i, last);
break;
case 'f':
text += '\x66';
advance(i, last);
break;
case 'n':
text += '\x6e';
advance(i, last);
break;
case 'r':
text += '\x72';
advance(i, last);
break;
case 't':
text += '\x74';
advance(i, last);
break;
case 'u': {
advance(i, last); // skip u
std::uint32_t u[4];
for (int index = 0; index < 4; ++index, advance(i, last)) {
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
std::uint32_t c = (unsigned char)*i;
if (U'0' <= c && c <= U'9') {
u[index] = c - U'0';
}
else if (U'a' <= c && c <= U'f') {
u[index] = (c - U'a') + 10;
}
else if (U'A' <= c && c <= U'F') {
u[index] = (c - U'A') + 10;
}
else {
d.error(location(), "expected digit");
return variant::undefined;
}
}
std::uint32_t utf32 = u[0] << 12 | u[1] << 8 | u[2] << 4 | u[3];
if (0xdc00 <= utf32 && utf32 <= 0xdfff) {
d.error(location(), "invalid unicode code point");
return variant::undefined;
}
if (0xd800 <= utf32 && utf32 <= 0xdbff) {
// We found a high surrogate, expect a following low-surrogate.
auto high = utf32;
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != '\\') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last); // skip backslash
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != 'u') {
d.error(location(), "unexpected character2");
return variant::undefined;
}
advance(i, last); // skip 'u'
for (int index = 0; index < 4; ++index, advance(i, last)) {
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
std::uint32_t c = (unsigned char)*i;
if (U'0' <= c && c <= U'9') {
u[index] = c - U'0';
}
else if (U'a' <= c && c <= U'f') {
u[index] = (c - U'a') + 10;
}
else if (U'A' <= c && c <= U'F') {
u[index] = (c - U'A') + 10;
}
else {
d.error(location(), "expected digit");
return {};
}
}
std::uint32_t low = u[0] << 12 | u[1] << 8 | u[2] << 4 | u[3];
utf32 = (high << 10) + low - 0x35fdc00;
}
std::ostringstream str{ std::ios::out | std::ios::binary };
code::unicode::Utf8_encoder{}.encode(str, utf32);
text += str.str();
break;
}
}
continue;
}
if (is_control(i)) {
d.error(location(), "invalid character detected");
return variant::undefined;
}
text += *i++;
}
if (i == last) {
d.error(location(), "premature end");
return variant::undefined;
}
if (*i != '"') {
d.error(location(), "unexpected character");
return variant::undefined;
}
advance(i, last);
return variant{ std::move(loc), std::move(text) };
}
template< typename InputIterator >
variant
try_parse_any(diagnostics& d, InputIterator& i, InputIterator last)
{
auto loc = location();
if (i == last) {
d.error(loc, "premature end");
return variant::undefined;
}
switch (*i) {
case 'n':
return try_parse_undefined(d, i, last);
case '[':
return try_parse_array(d, i, last);
case 't':
case 'f':
return try_parse_boolean(d, i, last);
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return try_parse_number(d, i, last);
case '{':
return try_parse_object(d, i, last);
case '"':
return try_parse_string(d, i, last);
}
std::ostringstream str;
str << "unexpected character '" << *i << "'";
d.error(loc, str.str());
return variant::undefined;
}
template< typename InputIterator >
void
skip_whitespace(InputIterator& i, InputIterator last)
{
while (i != last && is_whitespace< InputIterator >(i))
advance(i, last);
}
template< typename InputIterator >
bool
is_whitespace(InputIterator const& c)
{
switch (*c) {
case 0x09: // tab
case 0x0a: // lf
case 0x0d: // cr
case 0x20: // space
return true;
}
return false;
}
template< typename InputIterator >
bool
is_control(InputIterator const& c)
{
switch (*c) {
case '\x00':
case '\x01':
case '\x02':
case '\x03':
case '\x04':
case '\x05':
case '\x06':
case '\x07':
case '\x08':
case '\x09':
case '\x0A':
case '\x0B':
case '\x0C':
case '\x0D':
case '\x0E':
case '\x0F':
case '\x10':
case '\x11':
case '\x12':
case '\x13':
case '\x14':
case '\x15':
case '\x16':
case '\x17':
case '\x18':
case '\x19':
case '\x1A':
case '\x1B':
case '\x1C':
case '\x1D':
case '\x1E':
case '\x1F':
return true;
}
return false;
}
diagnostics::location
location()
{
return diagnostics::location{ line_, column_ };
}
template< typename InputIterator >
void
advance(InputIterator& i, InputIterator last)
{
if (i == last)
return;
if (*i == '\r') {
++line_;
column_ = 1;
++i;
if (i != last && *i == '\n')
++i;
return;
}
if (*i == '\n') {
++line_;
column_ = 1;
++i;
return;
}
++column_;
++i;
}
std::uint32_t line_{ 1 };
std::uint32_t column_{ 1 };
};
} // namespace code::json
#endif

163
code/json/pointer.cxx Normal file
View File

@ -0,0 +1,163 @@
#include <code/json/pointer.hxx>
namespace code::json {
pointer::
pointer(std::string const& expression)
: refs_{parse_refs(expression)}
{}
optional< variant >
pointer::
read(variant const& source) const
{
auto const* current = &source;
auto refs = refs_;
while (refs.size() > 0) {
auto cref = refs.front();
refs.pop();
if (current->is_object()) {
if (!current->contains(cref))
return std::nullopt;
current = &current->get(cref);
}
else if (current->is_array()) {
if (cref.size() == 1 && '~' == cref[0])
throw invalid_array_index{ cref };
auto i = std::stoul(cref);
if (current->size() <= i)
throw invalid_array_index{ cref };
current = &current->get(i);
}
else {
throw unexpected_type{};
}
}
return *current;
}
void
pointer::
write(variant& target, variant value) const
{
if (!target.is_object())
throw std::invalid_argument{"expected object"};
auto* current = &target;
auto refs = refs_;
while (refs.size() > 1) {
auto cref = refs.front();
refs.pop();
// Check what current is.
if (current->is_object()) {
if (current->contains(cref)) {
current = &current->get(cref);
}
else {
current->set(cref, std::map< std::string, variant >{});
current = &current->get(cref);
}
}
else if (current->is_array()) {
if (cref.size() == 1 && '~' == cref[0])
throw invalid_array_index{ cref };
auto i = std::stoul(cref);
if (current->size() <= i)
throw invalid_array_index{ cref };
current = &current->get(i);
}
else {
throw unexpected_type{};
}
}
if (refs.empty())
throw std::invalid_argument{"invalid argument"};
auto cref = refs.front();
refs.pop();
if (current->is_object()) {
current->set(cref, value);
}
else if (current->is_array()) {
// TODO: Implement.
}
else {
throw unexpected_type{};
}
}
std::queue< std::string >
pointer::
parse_refs(std::string const& expression)
{
std::queue< std::string > refs;
for (auto it = std::begin(expression); it != std::end(expression);) {
if ('/' != *it)
throw invalid_syntax{};
++it;
std::string cref;
while (it != std::end(expression) && '/' != *it) {
switch (*it) {
case '~':
++it; // skip tilde
if (it == std::end(expression))
throw invalid_syntax{};
else if ('0' == *it)
cref += '~';
else if ('1' == *it)
cref += '/';
else
throw invalid_syntax{};
++it;
break;
default:
cref += *it;
++it;
break;
}
}
if (!cref.empty())
refs.push(std::move(cref));
}
return refs;
}
std::string
to_string(pointer const& ptr)
{
std::string rendered;
auto refs = ptr.refs_;
while (refs.size() > 0) {
auto cref = refs.front();
refs.pop();
rendered += '/';
rendered += cref;
}
return rendered;
}
} // namespace code::json

41
code/json/pointer.hxx Normal file
View File

@ -0,0 +1,41 @@
#ifndef code__json__pointer_hxx_
#define code__json__pointer_hxx_
#include <code/json/exception.hxx>
#include <code/json/optional.hxx>
#include <code/json/read.hxx>
#include <code/json/variant.hxx>
#include <queue>
#include <string>
namespace code::json {
class pointer {
public:
explicit
pointer(std::string const& expression);
optional< variant >
read(variant const& source) const;
void
write(variant& target, variant value) const;
friend
std::string
to_string(pointer const& ptr);
private:
std::queue< std::string >
parse_refs(std::string const& expression);
std::queue< std::string > refs_;
};
std::string
to_string(pointer const& ptr);
} // namespace code::json
#endif

View File

@ -0,0 +1,95 @@
#include <code/json/pointer.hxx>
#include <string>
#include <iostream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
auto resolve = [](std::string const& data, char const* path)
{
code::json::pointer ptr{std::string{path}};
return ptr.read(code::json::read(data));
};
DEFINE_TEST("simple object")
{
std::string json = R"JSON(
{ "haystack": "needle" }
)JSON";
auto variant = resolve(json, "/haystack");
TEST_TRUE(variant->is_string());
TEST_EQUAL(variant->get_string(), "needle");
}
DEFINE_TEST("simple array")
{
std::string json = R"JSON(
[ "needle" ]
)JSON";
auto variant = resolve(json, "/0");
TEST_TRUE(variant->is_string());
TEST_EQUAL(variant->get_string(), "needle");
}
DEFINE_TEST("complex")
{
std::string json = R"JSON(
{
"employees": [
{
"name": "Doe, John",
"departments": ["HQ"],
"age": 37
},
{
"name": "Doe, Jane",
"departments": ["Software", "Finance"],
"age": 29
}
]
}
)JSON";
auto emp_0_name = resolve(json, "/employees/0/name");
auto emp_0_department_0 = resolve(json, "/employees/0/departments/0");
auto emp_0_age = resolve(json, "/employees/0/age");
auto emp_1_name = resolve(json, "/employees/1/name");
auto emp_1_department_0 = resolve(json, "/employees/1/departments/0");
auto emp_1_department_1 = resolve(json, "/employees/1/departments/1");
auto emp_1_age = resolve(json, "/employees/1/age");
TEST_TRUE(emp_0_name->is_string());
TEST_TRUE(emp_0_department_0->is_string());
TEST_TRUE(emp_0_age->is_number());
TEST_TRUE(emp_0_age->is_unsigned());
TEST_TRUE(emp_1_name->is_string());
TEST_TRUE(emp_1_department_0->is_string());
TEST_TRUE(emp_1_department_1->is_string());
TEST_TRUE(emp_1_age->is_number());
TEST_TRUE(emp_1_age->is_unsigned());
TEST_EQUAL(emp_0_name->get_string(), "Doe, John");
TEST_EQUAL(emp_0_department_0->get_string(), "HQ");
TEST_EQUAL(emp_0_age->get_number< signed int >(), 37);
TEST_EQUAL(emp_0_age->get_number< unsigned int >(), 37);
TEST_EQUAL(emp_1_name->get_string(), "Doe, Jane");
TEST_EQUAL(emp_1_department_0->get_string(), "Software");
TEST_EQUAL(emp_1_department_1->get_string(), "Finance");
TEST_EQUAL(emp_1_age->get_number< signed int >(), 29);
TEST_EQUAL(emp_1_age->get_number< unsigned int >(), 29);
}
return 0;
}

74
code/json/read.hxx Normal file
View File

@ -0,0 +1,74 @@
#ifndef code__json__read_hxx_
#define code__json__read_hxx_
#include <code/json/optional.hxx>
#include <code/json/parser.hxx>
#include <code/json/variant.hxx>
#include <istream>
#include <iterator>
namespace code::json {
inline variant
read(std::string const& input)
{
diagnostics d;
auto first = input.begin();
return parser{}.parse(d, first, input.end());
}
inline variant
read(diagnostics& d, std::string const& input)
{
auto first = input.begin();
return parser{}.parse(d, first, input.end());
}
inline optional< variant >
try_read(diagnostics& d, std::string const& input)
{
auto first = input.begin();
return parser{}.try_parse(d, first, input.end());
}
inline optional< variant >
try_read(std::string const& input)
{
diagnostics d;
auto first = input.begin();
return parser{}.try_parse(d, first, input.end());
}
inline variant
read(diagnostics& d, std::istream& input)
{
input.unsetf(std::istream::skipws);
std::istreambuf_iterator< char > first{ input };
return parser{}.parse(d, first, std::istreambuf_iterator< char >{});
}
inline variant
read(std::istream& input)
{
diagnostics d;
return read(d, input);
}
inline optional< variant >
try_read(std::istream& input)
{
input.unsetf(std::istream::skipws);
diagnostics d;
std::istreambuf_iterator< char > first{ input };
return parser{}.try_parse(d, first, std::istreambuf_iterator< char >{});
}
} // namespace code::json
#endif

607
code/json/read.test.cxx Normal file
View File

@ -0,0 +1,607 @@
#include <code/json/parser.hxx>
#include <code/json/read.hxx>
#include <iostream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_TRUE(x) if (!(x)) return __LINE__;
#define TEST_FALSE(x) if ((x)) return __LINE__;
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("boolean: parse false")
{
std::string const text{ "false" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), false);
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
}
DEFINE_TEST("boolean: parse false, with whitespace")
{
std::string const text{ " false " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), false);
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 2);
}
DEFINE_TEST("boolean: parse true")
{
std::string const text{ "true" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), true);
}
DEFINE_TEST("boolean: parse true, with whitespace")
{
std::string const text{ " true " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_boolean());
TEST_EQUAL(var.get_boolean(), true);
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 2);
}
DEFINE_TEST("null: parse null")
{
std::string const text{ "null" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_undefined());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
}
DEFINE_TEST("null: parse null, with whitespace")
{
std::string const text{ " null " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_undefined());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 3);
}
DEFINE_TEST("array: parse empty")
{
std::string const text{ "[]" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_TRUE(var.empty());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
}
DEFINE_TEST("array: parse empty, with whitespace")
{
std::string const text{ " [ ] " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_TRUE(var.empty());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 2);
}
DEFINE_TEST("array: parse null element")
{
std::string const text{ "[null]" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_EQUAL(var.location().line, 1);
TEST_EQUAL(var.location().column, 1);
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 1);
TEST_TRUE(var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 2);
}
DEFINE_TEST("array: parse null element, with whitespace")
{
std::string const text{ " [ null ] " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 1);
TEST_TRUE(var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 4);
}
DEFINE_TEST("array: parse double null elements")
{
std::string const text{ "[null,null]" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 2);
TEST_TRUE(var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 2);
TEST_TRUE(var.get(1).is_undefined());
TEST_EQUAL(var.get(1).location().line, 1);
TEST_EQUAL(var.get(1).location().column, 7);
}
DEFINE_TEST("array: parse double null elements, with whitespace")
{
std::string const text{ " [ null , null ] " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE( var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 2);
TEST_TRUE( var.get(0).is_undefined());
TEST_EQUAL(var.get(0).location().line, 1);
TEST_EQUAL(var.get(0).location().column, 4);
TEST_TRUE( var.get(1).is_undefined());
TEST_EQUAL(var.get(1).location().line, 1);
TEST_EQUAL(var.get(1).location().column, 11);
}
DEFINE_TEST("array: parse nested")
{
std::string const text{ " [ [ null , null ] , [ null ] ]" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_array());
TEST_FALSE(var.empty());
TEST_EQUAL(var.size(), 2);
TEST_TRUE(var.get(0).is_array());
TEST_FALSE(var.get(0).empty());
TEST_EQUAL(var.get(0).size(), 2);
TEST_TRUE(var.get(0).get(0).is_undefined());
TEST_TRUE(var.get(0).get(1).is_undefined());
TEST_TRUE(var. get(1).is_array());
TEST_FALSE(var.get(1).empty());
TEST_EQUAL(var.get(1).size(), 1);
TEST_TRUE(var. get(1).get(0).is_undefined());
}
DEFINE_TEST("object: parse empty")
{
std::string const text{ "{}" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_TRUE(var.keys().empty());
}
DEFINE_TEST("object: parse empty, with whitespace")
{
std::string const text{ " { } " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 0UL);
}
DEFINE_TEST("object: parse 1-element object")
{
std::string const text{ "{\"key\":null}" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 1UL);
TEST_TRUE(var.get("key").is_undefined());
}
DEFINE_TEST("object: parse 1-element object (with whitespace)")
{
std::string const text{ " { \"key\" : null } " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 1UL);
TEST_TRUE(var.get("key").is_undefined());
}
DEFINE_TEST("object: parse 2-element object")
{
std::string const text{ "{\"key1\":null,\"key2\":null}" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 2UL);
TEST_TRUE(var.get("key1").is_undefined());
TEST_TRUE(var.get("key2").is_undefined());
}
DEFINE_TEST("object: parse 2-element object (with whitespace)")
{
std::string const text{ " { \"key1\" : null, \"key2\" : null } " };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 2UL);
TEST_TRUE(var.get("key1").is_undefined());
TEST_TRUE(var.get("key2").is_undefined());
}
DEFINE_TEST("object: parse nested object")
{
std::string const text{ R"#({
"k1": {
"k1": null
},
"k2": {
"k1": null,
"k2": null
}
})#" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_object());
TEST_EQUAL(var.keys().size(), 2UL);
// Nested object 1.
auto object1 = var.get("k1");
TEST_TRUE(object1.is_object());
TEST_EQUAL(object1.keys().size(), 1UL);
TEST_TRUE(object1.get("k1").is_undefined());
// Nested object 2.
auto object2 = var.get("k2");
TEST_TRUE(object2.is_object());
TEST_EQUAL(object2.keys().size(), 2UL);
TEST_TRUE(object2.get("k1").is_undefined());
TEST_TRUE(object2.get("k2").is_undefined());
}
DEFINE_TEST("number: integer: 0")
{
std::string const text{ "0" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_FALSE(var.is_signed());
TEST_TRUE(var.is_unsigned());
TEST_EQUAL(var.get_number< unsigned long long >(), 0);
}
DEFINE_TEST("number: integer: 1")
{
std::string const text{ "1" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_FALSE(var.is_signed());
TEST_TRUE(var.is_unsigned());
TEST_EQUAL(var.get_number< unsigned long long >(), 1);
}
DEFINE_TEST("number: integer: -1")
{
std::string const text{ "-1" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_signed());
TEST_FALSE(var.is_unsigned());
TEST_EQUAL(var.get_number< long long >(), -1);
}
DEFINE_TEST("number: integer: 18446744073709551615")
{
std::string const text{ "18446744073709551615" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_unsigned());
TEST_FALSE(var.is_signed());
TEST_EQUAL(var.get_number< unsigned long long >(), 18446744073709551615ULL);
}
DEFINE_TEST("number: real: 0.0")
{
std::string const text{ "0.0" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 0.0);
}
DEFINE_TEST("number: real: 1.0")
{
std::string const text{ "1.0" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1.0);
}
DEFINE_TEST("number: real: 1e3")
{
std::string const text{ "1e3" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1E3")
{
std::string const text{ "1E3" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1e+3")
{
std::string const text{ "1e+3" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1E+3")
{
std::string const text{ "1E+3" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 1000.0);
}
DEFINE_TEST("number: real: 1e-3")
{
std::string const text{ "1e-3" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 0.001);
}
DEFINE_TEST("number: real: 1E-3")
{
std::string const text{ "1E-3" };
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_real());
TEST_EQUAL(var.get_number< double >(), 0.001);
}
DEFINE_TEST("string: empty string")
{
code::json::diagnostics diag;
auto var = code::json::read(R"("")");
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_string());
TEST_EQUAL(var.get_string(), "");
}
DEFINE_TEST("string: escape sequences")
{
std::pair< std::string, std::string > escapes[] = {
{ R"("\"")", "\x22" }, // " (quotation mark)
{ R"("\\")", "\x5C" }, // \ (reverse solidus)
{ R"("\/")", "\x2F" }, // / (solidus)
{ R"("\b")", "\x62" }, // b (backspace)
{ R"("\f")", "\x66" }, // f (form feed)
{ R"("\n")", "\x6E" }, // n (line feed)
{ R"("\r")", "\x72" }, // r (carriage return)
{ R"("\t")", "\x74" } // t (tab)
};
for (auto const& esc : escapes) {
code::json::diagnostics diag;
auto var = code::json::read(esc.first);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_string());
TEST_EQUAL(var.get_string(), esc.second);
}
}
DEFINE_TEST("string: utf-16 escape sequences")
{
std::pair< std::string, std::string > escapes[]{
{ R"("{\ud834\udd1e}")", "{\xf0\x9d\x84\x9e}" }, // MUSICAL SYMBOL G CLEF
};
for (auto const& escape : escapes) {
std::string text = escape.first;
code::json::diagnostics diag;
auto var = code::json::read(diag, text);
TEST_EQUAL(diag.errors().size(), 0);
TEST_EQUAL(diag.warnings().size(), 0);
TEST_TRUE(var.is_string());
TEST_EQUAL(var.get_string(), escape.second);
}
}
return 0;
}

View File

@ -0,0 +1,23 @@
#include <code/json/pointer.hxx>
#include <code/json/write.hxx>
#include <string>
#include <iostream>
int
main(int argc, char* argv[])
{
#if 0
std::stringstream str;
str << std::cin.rdbuf();
auto var = code::json::read(str.str());
std::cout << code::json::write(var);
code::json::resolve(str.str(), argv[1]);
return 0;
#endif
}

285
code/json/variant.hxx Normal file
View File

@ -0,0 +1,285 @@
#ifndef code__json__variant_hxx_
#define code__json__variant_hxx_
#include <code/json/diagnostics.hxx>
#include <code/json/exception.hxx>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <variant>
#include <vector>
class emitter;
namespace code::json {
//! Variant holding any valid JSON value.
class variant {
public:
//! Array iterator type.
using iterator = std::vector< variant >::iterator;
//! Array iterator type.
using const_iterator = std::vector< variant >::const_iterator;
//! Type used to denote an undefined variant.
struct undefined_t { };
static constexpr undefined_t undefined{};
//! Construct an undefined variant.
variant();
//! Construct an undefined variant.
variant(undefined_t);
//! Construct a variant holding a boolean value.
variant(bool value);
//! Construct a variant holding a signed number.
variant(short int value);
//! Construct a variant holding a signed number.
variant(int value);
//! Construct a variant holding a signed number.
variant(long int value);
//! Construct a variant holding a signed number.
variant(long long int value);
//! Construct a variant holding an unsigned number.
variant(unsigned short int value);
//! Construct a variant holding an unsigned number.
variant(unsigned int value);
//! Construct a variant holding an unsigned number.
variant(unsigned long int value);
//! Construct a variant holding an unsigned number.
variant(unsigned long long int value);
//! Construct a variant holding a real number.
variant(float value);
//! Construct a variant holding a real number.
variant(double value);
//! Construct a variant holding a real number.
variant(long double value);
//! Construct a variant holding a string.
variant(std::string value);
//! Construct a variant holding a string.
variant(char const* value);
//! Construct a variant holding an array.
variant(std::vector< variant > value);
//! Construct a variant holding an object.
variant(std::map< std::string, variant > value);
variant(diagnostics::location location, undefined_t);
variant(diagnostics::location location, bool value);
variant(diagnostics::location location, short int value);
variant(diagnostics::location location, int value);
variant(diagnostics::location location, long int value);
variant(diagnostics::location location, long long int value);
variant(diagnostics::location location, unsigned short int value);
variant(diagnostics::location location, unsigned int value);
variant(diagnostics::location location, unsigned long int value);
variant(diagnostics::location location, unsigned long long int value);
variant(diagnostics::location location, float value);
variant(diagnostics::location location, double value);
variant(diagnostics::location location, long double value);
variant(diagnostics::location location, std::string value);
variant(diagnostics::location location, char const* value);
variant(diagnostics::location location, std::vector< variant > value);
variant(diagnostics::location location, std::map< std::string, variant > value);
//! Check if this variant is undefined.
bool
is_undefined() const;
//! Check if this variant holds a boolean.
bool
is_boolean() const;
//! Get boolean value.
bool
get_boolean() const;
//! Check if this variant holds a number value.
bool
is_number() const;
//! Check if this variant holds a signed value.
bool
is_signed() const;
//! Check if this variant holds an unsigned value.
bool
is_unsigned() const;
//! Check if this variant holds a real value.
bool
is_real() const;
//! Get number value.
template< typename ArithmeticType >
ArithmeticType
get_number() const;
//! Check if this variant holds a string.
bool
is_string() const;
//! Get string value.
std::string const&
get_string() const;
//! Check if this variant holds an array.
bool
is_array() const;
//! Check if empty.
bool
empty() const;
//! Get array size.
std::size_t
size() const;
//! Get array iterator.
iterator
begin();
//! Get array iterator.
const_iterator
begin() const;
//! Get array iterator.
const_iterator
cbegin() const;
//! Get array iterator.
iterator
end();
//! Get array iterator.
const_iterator
end() const;
//! Get array iterator.
const_iterator
cend() const;
//! Get value at array index.
variant&
get(std::size_t index);
//! Get value at array index.
variant const&
get(std::size_t index) const;
//! Get value at array index.
variant const&
cget(std::size_t index) const;
//! Push value to back of array.
void
push_back(variant v);
//! Remove element from array.
void
erase(std::size_t index);
//! Check if this variant holds an object.
bool
is_object() const;
//! Check if object contains key.
bool
contains(std::string const& key) const;
//! Get object keys.
std::set< std::string >
keys() const;
//! Get object value.
variant&
get(std::string const& key);
//! Get object value.
variant const&
get(std::string const& key) const;
//! Get object value.
variant const&
cget(std::string const& key) const;
//! Set object value.
void
set(std::string const& key, variant v);
//! Remove element from object.
void
erase(std::string const& key);
//! Get the location of this value.
diagnostics::location const&
location() const;
template< typename Visitor >
friend void
visit(Visitor&& visitor, variant const& value)
{
std::visit(std::forward< Visitor >(visitor), value.value_);
}
private:
friend class emitter;
//! Internal variant type.
using variant_type = std::variant< undefined_t,
bool,
long long int,
unsigned long long int,
long double,
std::string,
std::vector< variant >,
std::map< std::string, variant > >;
//! Optional source location of this variant.
diagnostics::location location_;
//! The value.
variant_type value_;
};
} // namespace code::json
#include <code/json/variant.ixx>
#include <code/json/variant.txx>
#endif

409
code/json/variant.ixx Normal file
View File

@ -0,0 +1,409 @@
namespace code::json {
inline variant::variant() : value_{ undefined_t{} }
{}
inline variant::variant(undefined_t) : value_{ undefined_t{} }
{}
inline variant::variant(bool value) : value_{ value }
{}
inline variant::variant(short int value)
: value_{ static_cast< long long int >(value) }
{}
inline variant::variant(int value)
: value_{ static_cast< long long int >(value) }
{}
inline variant::variant(long int value)
: value_{ static_cast< long long int >(value) }
{}
inline variant::variant(long long int value) : value_{ value }
{}
inline variant::variant(unsigned short int value)
: value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(unsigned int value)
: value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(unsigned long int value)
: value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(unsigned long long int value) : value_{ value }
{}
inline variant::variant(float value)
: value_{ static_cast< long double >(value) }
{}
inline variant::variant(double value)
: value_{ static_cast< long double >(value) }
{}
inline variant::variant(long double value) : value_{ value }
{}
inline variant::variant(std::string value) : value_{ value }
{}
inline variant::variant(char const* value) : value_{ std::string{ value } }
{}
inline variant::variant(std::vector< variant > value) : value_{ std::move(value) }
{}
inline variant::variant(std::map< std::string, variant > value) : value_{ std::move(value) }
{}
inline variant::variant(diagnostics::location location, undefined_t)
: location_{ std::move(location) }, value_{ undefined_t{} }
{}
inline variant::variant(diagnostics::location location, bool value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location, short int value)
: location_{ std::move(location) },
value_{ static_cast< long long int >(value) }
{}
inline variant::variant(diagnostics::location location, int value)
: location_{ std::move(location) },
value_{ static_cast< long long int >(value) }
{}
inline variant::variant(diagnostics::location location, long int value)
: location_{ std::move(location) },
value_{ static_cast< long long int >(value) }
{}
inline variant::variant(diagnostics::location location, long long int value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location,
unsigned short int value)
: location_{ std::move(location) },
value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(diagnostics::location location, unsigned int value)
: location_{ std::move(location) },
value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(diagnostics::location location, unsigned long int value)
: location_{ std::move(location) },
value_{ static_cast< unsigned long long int >(value) }
{}
inline variant::variant(diagnostics::location location,
unsigned long long int value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location, float value)
: location_{ std::move(location) },
value_{ static_cast< long double >(value) }
{}
inline variant::variant(diagnostics::location location, double value)
: location_{ std::move(location) },
value_{ static_cast< long double >(value) }
{}
inline variant::variant(diagnostics::location location, long double value)
: location_{ std::move(location) }, value_{ value }
{}
inline variant::variant(diagnostics::location location, std::string value)
: location_{ std::move(location) }, value_{ std::move(value) }
{}
inline variant::variant(diagnostics::location location, char const* value)
: location_{ std::move(location) }, value_{ std::string{ value } }
{}
inline variant::variant(diagnostics::location location, std::vector< variant > value)
: location_{ std::move(location) }, value_{ std::move(value) }
{}
inline variant::variant(diagnostics::location location, std::map< std::string, variant > value)
: location_{ std::move(location) }, value_{ std::move(value) }
{}
inline bool
variant::is_undefined() const
{
return std::holds_alternative< undefined_t >(value_);
}
inline bool
variant::is_boolean() const
{
return std::holds_alternative< bool >(value_);
}
inline bool
variant::get_boolean() const
{
if (std::holds_alternative< bool >(value_))
return std::get< bool >(value_);
throw invalid_type{};
}
inline bool
variant::is_number() const
{
return is_signed() || is_unsigned() || is_real();
}
inline bool
variant::is_signed() const
{
return std::holds_alternative< long long int >(value_);
}
inline bool
variant::is_unsigned() const
{
return std::holds_alternative< unsigned long long int >(value_);
}
inline bool
variant::is_real() const
{
return std::holds_alternative< long double >(value_);
}
inline bool
variant::is_string() const
{
return std::holds_alternative< std::string >(value_);
}
inline std::string const&
variant::get_string() const
{
if (std::holds_alternative< std::string >(value_))
return std::get< std::string >(value_);
throw invalid_type{};
}
inline bool
variant::is_array() const
{
return std::holds_alternative< std::vector< variant > >(value_);
}
inline bool
variant::empty() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).empty();
throw invalid_type{};
}
inline std::size_t
variant::size() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).size();
throw invalid_type{};
}
inline variant::iterator
variant::begin()
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).begin();
throw invalid_type{};
}
inline variant::const_iterator
variant::begin() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).begin();
throw invalid_type{};
}
inline variant::const_iterator
variant::cbegin() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).cbegin();
throw invalid_type{};
}
inline variant::iterator
variant::end()
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).end();
throw invalid_type{};
}
inline variant::const_iterator
variant::end() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).end();
throw invalid_type{};
}
inline variant::const_iterator
variant::cend() const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_).cend();
throw invalid_type{};
}
inline variant&
variant::get(std::size_t index)
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_)[index];
throw invalid_type{};
}
inline variant const&
variant::get(std::size_t index) const
{
if (std::holds_alternative< std::vector< variant > >(value_))
return std::get< std::vector< variant > >(value_)[index];
throw invalid_type{};
}
inline variant const&
variant::cget(std::size_t index) const
{
return get(index);
}
inline void
variant::push_back(variant v)
{
if (!std::holds_alternative< std::vector< variant > >(value_))
throw invalid_type{};
std::get< std::vector< variant > >(value_).push_back(std::move(v));
}
inline void
variant::erase(std::size_t index)
{
if (!std::holds_alternative< std::vector< variant > >(value_))
throw invalid_type{};
auto& v = std::get< std::vector< variant > >(value_);
if (index >= v.size())
throw std::out_of_range{"index"};
v.erase(v.begin() + index);
}
inline bool
variant::is_object() const
{
return std::holds_alternative< std::map< std::string, variant > >(value_);
}
inline bool
variant::contains(std::string const& key) const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).count(key) > 0;
throw invalid_type{};
}
inline std::set< std::string >
variant::keys() const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_)) {
std::set< std::string > keys;
for (auto const& j : std::get< std::map< std::string, variant > >(value_))
keys.emplace(j.first);
return keys;
}
throw invalid_type{};
}
inline variant&
variant::get(std::string const& key)
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).at(key);
throw invalid_type{};
}
inline variant const&
variant::get(std::string const& key) const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).at(key);
throw invalid_type{};
}
inline variant const&
variant::cget(std::string const& key) const
{
if (std::holds_alternative< std::map< std::string, variant > >(value_))
return std::get< std::map< std::string, variant > >(value_).at(key);
throw invalid_type{};
}
inline void
variant::set(std::string const& key, variant v)
{
if (!std::holds_alternative< std::map< std::string, variant > >(value_))
throw invalid_type{};
std::get< std::map< std::string, variant > >(value_)[key] = std::move(v);
}
inline void
variant::erase(std::string const& key)
{
if (!std::holds_alternative< std::map< std::string, variant > >(value_))
throw invalid_type{};
std::get< std::map< std::string, variant > >(value_).erase(key);
}
inline diagnostics::location const&
variant::location() const
{
return location_;
}
} // namespace code::json

19
code/json/variant.txx Normal file
View File

@ -0,0 +1,19 @@
namespace code::json {
template< typename ArithmeticType >
ArithmeticType
variant::get_number() const
{
if constexpr (std::is_arithmetic_v< ArithmeticType >) {
if (std::holds_alternative< long long int >(value_))
return static_cast< ArithmeticType >(std::get< long long int >(value_));
else if (std::holds_alternative< unsigned long long int >(value_))
return static_cast< ArithmeticType >(std::get< unsigned long long int >(value_));
else if (std::holds_alternative< long double >(value_))
return static_cast< ArithmeticType >(std::get< long double >(value_));
}
throw invalid_type{};
}
} // namespace code::json

37
code/json/version.hxx.in Normal file
View File

@ -0,0 +1,37 @@
#ifndef code__json__version_hxx_
#define code__json__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBCODE_JSON_VERSION $libcode_json.version.project_number$ULL
#define LIBCODE_JSON_VERSION_STR "$libcode_json.version.project$"
#define LIBCODE_JSON_VERSION_ID "$libcode_json.version.project_id$"
#define LIBCODE_JSON_VERSION_FULL "$libcode_json.version$"
#define LIBCODE_JSON_VERSION_MAJOR $libcode_json.version.major$
#define LIBCODE_JSON_VERSION_MINOR $libcode_json.version.minor$
#define LIBCODE_JSON_VERSION_PATCH $libcode_json.version.patch$
#define LIBCODE_JSON_PRE_RELEASE $libcode_json.version.pre_release$
#define LIBCODE_JSON_SNAPSHOT_SN $libcode_json.version.snapshot_sn$ULL
#define LIBCODE_JSON_SNAPSHOT_ID "$libcode_json.version.snapshot_id$"
#endif

31
code/json/write.hxx Normal file
View File

@ -0,0 +1,31 @@
#ifndef code__json__write_hxx_
#define code__json__write_hxx_
#include <code/json/emitter.hxx>
#include <ostream>
#include <sstream>
#include <string>
namespace code::json {
class variant;
inline void
write(std::ostream& os, variant const& v)
{
visit(emitter{ os }, v);
os << '\n';
}
inline std::string
write(variant const& json)
{
std::stringstream str;
write(str, json);
return str.str();
}
} // namespace code::json
#endif

31
code/json/write.test.cxx Normal file
View File

@ -0,0 +1,31 @@
#include <code/json/variant.hxx>
#include <code/json/write.hxx>
#include <iostream>
#include <sstream>
#define DEFINE_TEST(x) std::cout << x << '\n';
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
int
main()
{
DEFINE_TEST("write")
{
auto document = std::map< std::string, code::json::variant >{
{ { "title", "Coca-Cola Regular 1.5L" },
{ "gtin13", "5449000139306" } }
};
std::stringstream output;
code::json::write(output, document);
TEST_EQUAL(output.str(), R"({
"gtin13": "5449000139306",
"title": "Coca-Cola Regular 1.5L"
}
)");
}
return 0;
}

13
manifest Normal file
View File

@ -0,0 +1,13 @@
: 1
name: libcode-json
version: 0.1.0-a.0.z
language: c++
summary: libcode-json C++ library
license: BSD-4-Clause
description-file: README.md
url: https://helloryan.se/code/
email: ryan@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libcode-validation ^0.1.0-
depends: libcode-unicode ^0.1.0-

10
repositories.manifest Normal file
View File

@ -0,0 +1,10 @@
: 1
summary: libcode-uri project repository
:
role: prerequisite
location: https://code.helloryan.se/code/libcode-validation.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/code/libcode-unicode.git##HEAD

8
tests/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@ -0,0 +1 @@
./: {*/ -build/}