You've already forked libart-json
This commit is contained in:
17
.editorconfig
Normal file
17
.editorconfig
Normal 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
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
||||||
31
.gitea/workflows/on-push.yaml
Normal file
31
.gitea/workflows/on-push.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: on-push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags-ignore:
|
||||||
|
- '*'
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: linux
|
||||||
|
container: code.helloryan.se/art/infra/buildenv/x86_64-fedora_42-unified:latest
|
||||||
|
volumes:
|
||||||
|
- /build
|
||||||
|
steps:
|
||||||
|
- name: Configure repository access
|
||||||
|
run: |
|
||||||
|
git config --global http.$GITHUB_SERVER_URL/.extraheader "Authorization: token ${{ secrets.ACT_RUNNER_TOKEN }}"
|
||||||
|
- name: Configure build directory
|
||||||
|
run: |
|
||||||
|
bpkg create -d /build cc config.cxx=clang++ config.cc.coptions="-Wall -Werror -Wno-unknown-pragmas"
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
cd /build
|
||||||
|
bpkg build --yes --trust-yes $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git##$GITHUB_SHA
|
||||||
|
- name: Test package
|
||||||
|
run: |
|
||||||
|
cd /build
|
||||||
|
b test
|
||||||
|
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
patreon: helloryan
|
||||||
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal 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
31
LICENSE
Normal 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.
|
||||||
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# libart-json
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
libart-json implements JSON serialization/deserialization and parsing for C++.
|
||||||
|
|
||||||
|
## Sponsorship
|
||||||
|
|
||||||
|
You can sponsor the development of this project via Patreon. Read more
|
||||||
|
over at https://patreon.com/helloryan.
|
||||||
9
art/json/.gitignore
vendored
Normal file
9
art/json/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Generated version header.
|
||||||
|
#
|
||||||
|
version.hxx
|
||||||
|
|
||||||
|
# Unit test executables and Testscript output directories
|
||||||
|
# (can be symlinks).
|
||||||
|
#
|
||||||
|
*.test
|
||||||
|
test-*.test
|
||||||
BIN
art/json/.swp
Normal file
BIN
art/json/.swp
Normal file
Binary file not shown.
65
art/json/buildfile
Normal file
65
art/json/buildfile
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
intf_libs = # Interface dependencies.
|
||||||
|
impl_libs = # Implementation dependencies.
|
||||||
|
test_libs = # Test dependencies.
|
||||||
|
|
||||||
|
import intf_libs =+ libart-unicode%lib{art-unicode}
|
||||||
|
|
||||||
|
./: lib{art-json}: libul{art-json}
|
||||||
|
|
||||||
|
libul{art-json}: {hxx ixx txx cxx}{** -**.test... -version} \
|
||||||
|
{hxx }{ version}
|
||||||
|
|
||||||
|
libul{art-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{art-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{art-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{art-json}: bin.lib.version = "-$version.project_id"
|
||||||
|
else
|
||||||
|
lib{art-json}: bin.lib.version = "-$version.major.$version.minor"
|
||||||
|
|
||||||
|
# Install into the art/json/ subdirectory of, say, /usr/include/
|
||||||
|
# recreating subdirectories.
|
||||||
|
#
|
||||||
|
{hxx ixx txx}{*}:
|
||||||
|
{
|
||||||
|
install = include/art/json/
|
||||||
|
install.subdirs = true
|
||||||
|
}
|
||||||
45
art/json/diagnostics.cxx
Normal file
45
art/json/diagnostics.cxx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include <art/json/diagnostics.hxx>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
43
art/json/diagnostics.hxx
Normal file
43
art/json/diagnostics.hxx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef art__json__diagnostics_hxx_
|
||||||
|
#define art__json__diagnostics_hxx_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
54
art/json/emitter.hxx
Normal file
54
art/json/emitter.hxx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#ifndef art__json__emitter_hxx_
|
||||||
|
#define art__json__emitter_hxx_
|
||||||
|
|
||||||
|
#include <art/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#include <art/json/emitter.ixx>
|
||||||
|
|
||||||
|
#endif
|
||||||
141
art/json/emitter.ixx
Normal file
141
art/json/emitter.ixx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
namespace art::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 art::json
|
||||||
115
art/json/emitter.test.cxx
Normal file
115
art/json/emitter.test.cxx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include <art/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;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(art::json::variant::undefined_t{});
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: true")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(true);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: false")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(false);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("signed number")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(-10LL);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "-10");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("unsigned number")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(100LL);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "100");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("real number")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(1.2L);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "1.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("string")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(std::string{ "hello" });
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "\"hello\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: []")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
emitter(std::vector< art::json::variant >{});
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: [null]")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
|
||||||
|
emitter(std::vector< art::json::variant >{ art::json::variant{} });
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "[\n null\n]");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: {}")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
|
||||||
|
emitter(std::map< std::string, art::json::variant >{});
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: {...}")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
art::json::emitter emitter{ str };
|
||||||
|
|
||||||
|
emitter(std::map< std::string, art::json::variant >{ { "", {} } });
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "{\n \"\": null\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
43
art/json/exception.cxx
Normal file
43
art/json/exception.cxx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#include <art/json/exception.hxx>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
59
art/json/exception.hxx
Normal file
59
art/json/exception.hxx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef art__json__exception_hxx_
|
||||||
|
#define art__json__exception_hxx_
|
||||||
|
|
||||||
|
#include <art/json/diagnostics.hxx>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
883
art/json/marshaling.hxx
Normal file
883
art/json/marshaling.hxx
Normal file
@@ -0,0 +1,883 @@
|
|||||||
|
#ifndef art__json__marshaling_hxx_
|
||||||
|
#define art__json__marshaling_hxx_
|
||||||
|
|
||||||
|
#include <art/json/optional.hxx>
|
||||||
|
#include <art/json/pointer.hxx>
|
||||||
|
#include <art/json/traits.hxx>
|
||||||
|
#include <art/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <deque>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <list>
|
||||||
|
#include <locale>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <iostream> // fixme: remove
|
||||||
|
|
||||||
|
namespace art::json
|
||||||
|
{
|
||||||
|
|
||||||
|
class marshaling_context_t
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual ~marshaling_context_t() = default;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct marshaling_traits;
|
||||||
|
|
||||||
|
template<std::size_t N>
|
||||||
|
struct member_name_t
|
||||||
|
{
|
||||||
|
constexpr member_name_t(char const (&str)[N])
|
||||||
|
{
|
||||||
|
std::copy_n(str, N, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const
|
||||||
|
str() const
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator std::string const() const
|
||||||
|
{
|
||||||
|
return str();
|
||||||
|
}
|
||||||
|
|
||||||
|
char name[N];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename>
|
||||||
|
struct member_traits;
|
||||||
|
|
||||||
|
template<typename T, typename M>
|
||||||
|
struct member_traits<M T::*>
|
||||||
|
{
|
||||||
|
using class_type = T;
|
||||||
|
using member_type = M;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename, typename = std::void_t<>>
|
||||||
|
struct has_validator
|
||||||
|
: std::false_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct has_validator<
|
||||||
|
T,
|
||||||
|
std::void_t<
|
||||||
|
decltype(std::declval<T const&>().validate(std::declval<marshaling_context_t*>()))
|
||||||
|
>
|
||||||
|
>
|
||||||
|
: std::true_type
|
||||||
|
{};
|
||||||
|
|
||||||
|
template<member_name_t Name, auto Member, typename Validator = void>
|
||||||
|
struct member_t
|
||||||
|
{
|
||||||
|
using T = member_traits<decltype(Member)>::class_type;
|
||||||
|
using M = member_traits<decltype(Member)>::member_type;
|
||||||
|
using V = Validator;
|
||||||
|
|
||||||
|
static constexpr bool is_optional{
|
||||||
|
is_optional_v<M>
|
||||||
|
};
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
marshal(variant& v, T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
v.set(Name, marshaling_traits<M>::marshal(instance.*Member, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
unmarshal(T& instance, variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
if (!v.contains(Name.str())) {
|
||||||
|
if (is_optional) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error{"missing field '" + Name.str() + "'"};
|
||||||
|
}
|
||||||
|
|
||||||
|
M value = marshaling_traits<M>::unmarshal(v.get(Name), context);
|
||||||
|
|
||||||
|
if constexpr (!std::is_same_v<V, void>) {
|
||||||
|
V::validate(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.*Member = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pointer_t
|
||||||
|
{
|
||||||
|
using T = member_traits<decltype(Member)>::class_type;
|
||||||
|
using M = member_traits<decltype(Member)>::member_type;
|
||||||
|
|
||||||
|
static pointer const&
|
||||||
|
ptr()
|
||||||
|
{
|
||||||
|
static pointer ptr{Name};
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
marshal(variant& v, T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
ptr().write(v, marshaling_traits<M>::marshal(instance.*Member, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
unmarshal(T& instance, variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
auto ptr_v = ptr().read(v);
|
||||||
|
|
||||||
|
if (!ptr_v) {
|
||||||
|
if (is_optional) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error{ "missing field '" + to_string(ptr()) + "'" };
|
||||||
|
}
|
||||||
|
|
||||||
|
M value = marshaling_traits<M>::unmarshal(*ptr_v, context);
|
||||||
|
|
||||||
|
if constexpr (!std::is_same_v<V, void>) {
|
||||||
|
V::validate(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.*Member = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename First, typename... Members>
|
||||||
|
struct mapping_t
|
||||||
|
{
|
||||||
|
using T = First::T;
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
do_marshal(variant& v, T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
First::marshal(v, instance, context);
|
||||||
|
((Members::marshal(v, instance, context)), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
variant v{std::map<std::string, variant>{}};
|
||||||
|
do_marshal(v, instance, context);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
do_unmarshal(T& instance, variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
First::unmarshal(instance, v, context);
|
||||||
|
((Members::unmarshal(instance, v, context)), ...);
|
||||||
|
|
||||||
|
if constexpr (has_validator<T>::value) {
|
||||||
|
instance.validate(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
T
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
T instance;
|
||||||
|
do_unmarshal(instance, v, context);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Bases>
|
||||||
|
struct inherit_t
|
||||||
|
{
|
||||||
|
static_assert(sizeof...(Bases) > 0, "at least one base must be specified");
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
do_marshal(variant& v, T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
((Bases::json::do_marshal(v, instance, context)), ...);
|
||||||
|
First::marshal(v, instance, context);
|
||||||
|
((Members::marshal(v, instance, context)), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
variant v{std::map<std::string, variant>{}};
|
||||||
|
do_marshal(v, instance, context);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
do_unmarshal(T& instance, variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
((Bases::json::do_unmarshal(instance, v, context)), ...);
|
||||||
|
First::unmarshal(instance, v, context);
|
||||||
|
((Members::unmarshal(instance, v, context)), ...);
|
||||||
|
|
||||||
|
if constexpr (has_validator<T>::value) {
|
||||||
|
instance.validate(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
T
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
T instance;
|
||||||
|
do_unmarshal(instance, v, context);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<member_name_t Name, typename Type>
|
||||||
|
struct emplace_member_t
|
||||||
|
{
|
||||||
|
using T = Type;
|
||||||
|
|
||||||
|
static constexpr bool is_optional{
|
||||||
|
is_optional_v<T>
|
||||||
|
};
|
||||||
|
|
||||||
|
static
|
||||||
|
T
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
if (!v.contains(Name.str())) {
|
||||||
|
if constexpr (is_optional) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error{"missing field '" + Name.str() + "'"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return marshaling_traits<T>::unmarshal(v.get(Name), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename... Members>
|
||||||
|
struct emplace_t
|
||||||
|
{
|
||||||
|
static
|
||||||
|
T
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
return T{
|
||||||
|
Members::unmarshal(v, context)...
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct marshaling_traits
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(T const& instance, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
return T::json::marshal(instance, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
T
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
return T::json::unmarshal(v, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<variant>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct marshaling_traits<optional<T>>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(optional<T> const& model, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
if (model) {
|
||||||
|
return marshaling_traits<T>::marshal(*model, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static optional<T>
|
||||||
|
unmarshal(variant const& value, marshaling_context_t* context)
|
||||||
|
{
|
||||||
|
if (value.is_undefined()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return marshaling_traits<T>::unmarshal(value, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... T>
|
||||||
|
struct marshaling_traits<std::variant<T...>>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::variant<T...> const& model, marshaling_context_t* 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_t* 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_t* context)
|
||||||
|
{
|
||||||
|
return unmarshaler<T...>::unmarshal(value, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct marshaling_traits<std::vector<T>>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::vector<T> const& model, marshaling_context_t* 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_t* 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>>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::list<T> const& model, marshaling_context_t* 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_t* 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>>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::deque<T> const& model, marshaling_context_t* 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_t* 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>>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::set<T> const& model, marshaling_context_t* 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_t* 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<bool>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(bool model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
bool
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_boolean())
|
||||||
|
throw std::runtime_error{"not a boolean"};
|
||||||
|
|
||||||
|
return value.get_boolean();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<short int> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(short int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
short int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<short int>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<int>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
int
|
||||||
|
unmarshal(variant const& v, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!v.is_number()) {
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.get_number<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<long int> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(long int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
long int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<long int>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<long long int> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(long long int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
long long int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
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> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(unsigned short int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
unsigned short int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<unsigned short int>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<unsigned int> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(unsigned int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
unsigned int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<unsigned int>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<unsigned long int> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(unsigned long int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
unsigned long int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
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> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(unsigned long long int model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
unsigned long long int
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<unsigned long long int>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<float> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(float model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
float
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<float>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<double> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(double model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
double
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<double>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<long double> {
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(long double model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
long double
|
||||||
|
unmarshal(variant const& value, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{"not a number"};
|
||||||
|
|
||||||
|
return value.get_number<long double>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<std::string>
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::string const& model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::string
|
||||||
|
unmarshal(variant const& v, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
if (!v.is_string()) {
|
||||||
|
throw std::runtime_error{"not a string"};
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.get_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits<std::chrono::system_clock::time_point>
|
||||||
|
{
|
||||||
|
using model_type = std::string;
|
||||||
|
|
||||||
|
//static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
|
||||||
|
static constexpr const char time_format[] = "%Y-%m-%dT%H:%M:%S.000Z";
|
||||||
|
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(std::chrono::system_clock::time_point const& model, marshaling_context_t*)
|
||||||
|
{
|
||||||
|
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_t*)
|
||||||
|
{
|
||||||
|
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 T>
|
||||||
|
variant
|
||||||
|
marshal(T const& model, marshaling_context_t* context = nullptr)
|
||||||
|
{
|
||||||
|
return marshaling_traits<T>::marshal(model, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
unmarshal(variant const& v, marshaling_context_t* context = nullptr)
|
||||||
|
{
|
||||||
|
return marshaling_traits<T>::unmarshal(v, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
174
art/json/marshaling.test.cxx
Normal file
174
art/json/marshaling.test.cxx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#include <art/json/marshaling.hxx>
|
||||||
|
#include <art/json/optional.hxx>
|
||||||
|
#include <art/json/serialize.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__;
|
||||||
|
|
||||||
|
using art::json::mapping_t;
|
||||||
|
using art::json::marshal;
|
||||||
|
using art::json::marshaling_traits;
|
||||||
|
using art::json::member_t;
|
||||||
|
using art::json::unmarshal;
|
||||||
|
|
||||||
|
struct name_t
|
||||||
|
{
|
||||||
|
std::string first;
|
||||||
|
std::string last;
|
||||||
|
|
||||||
|
using json = mapping_t<
|
||||||
|
member_t<"/person/first", &name_t::first>::pointer_t,
|
||||||
|
member_t<"/person/last", &name_t::last>::pointer_t
|
||||||
|
>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("optional<int>{}")
|
||||||
|
{
|
||||||
|
using traits_type = marshaling_traits<art::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 = marshaling_traits<art::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 = 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 = 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 = 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 = 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 = 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 = 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 = 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("pointer")
|
||||||
|
{
|
||||||
|
name_t name1{"Jane", "Doe"};
|
||||||
|
auto v = marshal<name_t>(name1);
|
||||||
|
auto name2 = unmarshal<name_t>(v);
|
||||||
|
|
||||||
|
TEST_EQUAL(name1.first, name2.first);
|
||||||
|
TEST_EQUAL(name1.last, name2.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
30
art/json/optional.hxx
Normal file
30
art/json/optional.hxx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef art__json__optional_hxx_
|
||||||
|
#define art__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 art::json {
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
using std::optional;
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
using std::experimental::optional;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
759
art/json/parser.hxx
Normal file
759
art/json/parser.hxx
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
#ifndef art__json__parser_hxx_
|
||||||
|
#define art__json__parser_hxx_
|
||||||
|
|
||||||
|
#include <art/json/diagnostics.hxx>
|
||||||
|
#include <art/json/exception.hxx>
|
||||||
|
#include <art/json/optional.hxx>
|
||||||
|
#include <art/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <art/unicode/utf8-decoder.hxx>
|
||||||
|
#include <art/unicode/utf8-encoder.hxx>
|
||||||
|
|
||||||
|
#include <iostream> // TODO remove
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace art::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::string str;
|
||||||
|
unicode::string_writer_t writer{str};
|
||||||
|
unicode::utf8_encoder_t encoder{writer};
|
||||||
|
encoder.encode(utf32);
|
||||||
|
|
||||||
|
text += 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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
163
art/json/pointer.cxx
Normal file
163
art/json/pointer.cxx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include <art/json/pointer.hxx>
|
||||||
|
|
||||||
|
namespace art::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 = ¤t->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 = ¤t->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 = ⌖
|
||||||
|
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 = ¤t->get(cref);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
current->set(cref, std::map< std::string, variant >{});
|
||||||
|
current = ¤t->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 = ¤t->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 art::json
|
||||||
41
art/json/pointer.hxx
Normal file
41
art/json/pointer.hxx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef art__json__pointer_hxx_
|
||||||
|
#define art__json__pointer_hxx_
|
||||||
|
|
||||||
|
#include <art/json/exception.hxx>
|
||||||
|
#include <art/json/optional.hxx>
|
||||||
|
#include <art/json/read.hxx>
|
||||||
|
#include <art/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
95
art/json/pointer.test.cxx
Normal file
95
art/json/pointer.test.cxx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#include <art/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)
|
||||||
|
{
|
||||||
|
art::json::pointer ptr{std::string{path}};
|
||||||
|
return ptr.read(art::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
art/json/read.hxx
Normal file
74
art/json/read.hxx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#ifndef art__json__read_hxx_
|
||||||
|
#define art__json__read_hxx_
|
||||||
|
|
||||||
|
#include <art/json/optional.hxx>
|
||||||
|
#include <art/json/parser.hxx>
|
||||||
|
#include <art/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
607
art/json/read.test.cxx
Normal file
607
art/json/read.test.cxx
Normal file
@@ -0,0 +1,607 @@
|
|||||||
|
#include <art/json/parser.hxx>
|
||||||
|
#include <art/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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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{ "[]" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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{ " [ ] " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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]" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 ] " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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]" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 ] " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 ] ]" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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{ "{}" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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{ " { } " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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}" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 } " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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}" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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 } " };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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
|
||||||
|
}
|
||||||
|
})#" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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" };
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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")
|
||||||
|
{
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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) {
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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;
|
||||||
|
|
||||||
|
art::json::diagnostics diag;
|
||||||
|
auto var = art::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;
|
||||||
|
}
|
||||||
23
art/json/resolve.test.cxx
Normal file
23
art/json/resolve.test.cxx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#include <art/json/pointer.hxx>
|
||||||
|
#include <art/json/write.hxx>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
std::stringstream str;
|
||||||
|
str << std::cin.rdbuf();
|
||||||
|
|
||||||
|
auto var = art::json::read(str.str());
|
||||||
|
|
||||||
|
std::cout << art::json::write(var);
|
||||||
|
|
||||||
|
art::json::resolve(str.str(), argv[1]);
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
84
art/json/serialize.hxx
Normal file
84
art/json/serialize.hxx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#ifndef art__json__serialize_hxx_
|
||||||
|
#define art__json__serialize_hxx_
|
||||||
|
|
||||||
|
#include <art/json/marshaling.hxx>
|
||||||
|
|
||||||
|
#include <art/json/read.hxx>
|
||||||
|
#include <art/json/write.hxx>
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
|
namespace art::json
|
||||||
|
{
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void
|
||||||
|
serialize(std::ostream& o, T const& model)
|
||||||
|
{
|
||||||
|
write(o, marshal(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_t* context = nullptr)
|
||||||
|
{
|
||||||
|
return unmarshal<T>(read(d, i), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
deserialize(std::istream& i, marshaling_context_t* context = nullptr)
|
||||||
|
{
|
||||||
|
return unmarshal<T>(read(i), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
deserialize(diagnostics& d,
|
||||||
|
std::istream&& i,
|
||||||
|
marshaling_context_t* context = nullptr)
|
||||||
|
{
|
||||||
|
return deserialize<T>(d, i, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
deserialize(std::istream&& i, marshaling_context_t* context = nullptr)
|
||||||
|
{
|
||||||
|
return deserialize<T>(i, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
deserialize(diagnostics& d,
|
||||||
|
std::string const& str,
|
||||||
|
marshaling_context_t* 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_t* context = nullptr)
|
||||||
|
{
|
||||||
|
// TODO use std::string overload of read
|
||||||
|
return deserialize<T>(
|
||||||
|
std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
53
art/json/serialize.test.cxx
Normal file
53
art/json/serialize.test.cxx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include <art/json/marshaling.hxx>
|
||||||
|
#include <art/json/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__;
|
||||||
|
|
||||||
|
using art::json::mapping_t;
|
||||||
|
using art::json::member_t;
|
||||||
|
using art::json::serialize;
|
||||||
|
using art::json::deserialize;
|
||||||
|
|
||||||
|
struct person_name {
|
||||||
|
std::string first;
|
||||||
|
std::string last;
|
||||||
|
|
||||||
|
using json = mapping_t<
|
||||||
|
member_t<"first", &person_name::first>,
|
||||||
|
member_t<"last", &person_name::last>
|
||||||
|
>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct person {
|
||||||
|
person_name name;
|
||||||
|
int age;
|
||||||
|
|
||||||
|
using json = mapping_t<
|
||||||
|
member_t<"name", &person::name>,
|
||||||
|
member_t<"age", &person::age>
|
||||||
|
>;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("round trip")
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
72
art/json/traits.hxx
Normal file
72
art/json/traits.hxx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#ifndef art__json__traits_hxx_
|
||||||
|
#define art__json__traits_hxx_
|
||||||
|
|
||||||
|
#include <art/json/optional.hxx>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace art::json {
|
||||||
|
|
||||||
|
// 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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
285
art/json/variant.hxx
Normal file
285
art/json/variant.hxx
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
#ifndef art__json__variant_hxx_
|
||||||
|
#define art__json__variant_hxx_
|
||||||
|
|
||||||
|
#include <art/json/diagnostics.hxx>
|
||||||
|
#include <art/json/exception.hxx>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class emitter;
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#include <art/json/variant.ixx>
|
||||||
|
#include <art/json/variant.txx>
|
||||||
|
|
||||||
|
#endif
|
||||||
409
art/json/variant.ixx
Normal file
409
art/json/variant.ixx
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
namespace art::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 art::json
|
||||||
19
art/json/variant.txx
Normal file
19
art/json/variant.txx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace art::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 art::json
|
||||||
37
art/json/version.hxx.in
Normal file
37
art/json/version.hxx.in
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef art__json__version_hxx_
|
||||||
|
#define art__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 LIBART_JSON_VERSION $libart_json.version.project_number$ULL
|
||||||
|
#define LIBART_JSON_VERSION_STR "$libart_json.version.project$"
|
||||||
|
#define LIBART_JSON_VERSION_ID "$libart_json.version.project_id$"
|
||||||
|
#define LIBART_JSON_VERSION_FULL "$libart_json.version$"
|
||||||
|
|
||||||
|
#define LIBART_JSON_VERSION_MAJOR $libart_json.version.major$
|
||||||
|
#define LIBART_JSON_VERSION_MINOR $libart_json.version.minor$
|
||||||
|
#define LIBART_JSON_VERSION_PATCH $libart_json.version.patch$
|
||||||
|
|
||||||
|
#define LIBART_JSON_PRE_RELEASE $libart_json.version.pre_release$
|
||||||
|
|
||||||
|
#define LIBART_JSON_SNAPSHOT_SN $libart_json.version.snapshot_sn$ULL
|
||||||
|
#define LIBART_JSON_SNAPSHOT_ID "$libart_json.version.snapshot_id$"
|
||||||
|
|
||||||
|
#endif
|
||||||
31
art/json/write.hxx
Normal file
31
art/json/write.hxx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef art__json__write_hxx_
|
||||||
|
#define art__json__write_hxx_
|
||||||
|
|
||||||
|
#include <art/json/emitter.hxx>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace art::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 art::json
|
||||||
|
|
||||||
|
#endif
|
||||||
31
art/json/write.test.cxx
Normal file
31
art/json/write.test.cxx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include <art/json/variant.hxx>
|
||||||
|
#include <art/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, art::json::variant >{
|
||||||
|
{ { "title", "Coca-Cola Regular 1.5L" },
|
||||||
|
{ "gtin13", "5449000139306" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::stringstream output;
|
||||||
|
art::json::write(output, document);
|
||||||
|
|
||||||
|
TEST_EQUAL(output.str(), R"({
|
||||||
|
"gtin13": "5449000139306",
|
||||||
|
"title": "Coca-Cola Regular 1.5L"
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
4
build/.gitignore
vendored
Normal file
4
build/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/config.build
|
||||||
|
/root/
|
||||||
|
/bootstrap/
|
||||||
|
build/
|
||||||
7
build/bootstrap.build
Normal file
7
build/bootstrap.build
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
project = libart-json
|
||||||
|
|
||||||
|
using version
|
||||||
|
using config
|
||||||
|
using test
|
||||||
|
using install
|
||||||
|
using dist
|
||||||
6
build/export.build
Normal file
6
build/export.build
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
$out_root/
|
||||||
|
{
|
||||||
|
include art/json/
|
||||||
|
}
|
||||||
|
|
||||||
|
export $out_root/art/json/$import.target
|
||||||
16
build/root.build
Normal file
16
build/root.build
Normal 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
5
buildfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
./: {art/ tests/} doc{README.md} legal{LICENSE} manifest
|
||||||
|
|
||||||
|
# Don't install tests.
|
||||||
|
#
|
||||||
|
tests/: install = false
|
||||||
13
manifest
Normal file
13
manifest
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
: 1
|
||||||
|
name: libart-json
|
||||||
|
version: 0.1.0-a.0.z
|
||||||
|
language: c++
|
||||||
|
summary: libart-json C++ library
|
||||||
|
license: BSD-4-Clause
|
||||||
|
description-file: README.md
|
||||||
|
url: https://art.helloryan.se/
|
||||||
|
email: art@helloryan.se
|
||||||
|
depends: * build2 >= 0.17.0
|
||||||
|
depends: * bpkg >= 0.17.0
|
||||||
|
depends: libart-validation ^0.1.0-
|
||||||
|
depends: libart-unicode ^0.1.0-
|
||||||
10
repositories.manifest
Normal file
10
repositories.manifest
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
: 1
|
||||||
|
summary: libart-json project repository
|
||||||
|
|
||||||
|
:
|
||||||
|
role: prerequisite
|
||||||
|
location: https://code.helloryan.se/art/libart-validation.git##HEAD
|
||||||
|
|
||||||
|
:
|
||||||
|
role: prerequisite
|
||||||
|
location: https://code.helloryan.se/art/libart-unicode.git##HEAD
|
||||||
8
tests/.gitignore
vendored
Normal file
8
tests/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Test executables.
|
||||||
|
#
|
||||||
|
driver
|
||||||
|
|
||||||
|
# Testscript output directories (can be symlinks).
|
||||||
|
#
|
||||||
|
test
|
||||||
|
test-*
|
||||||
4
tests/build/.gitignore
vendored
Normal file
4
tests/build/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/config.build
|
||||||
|
/root/
|
||||||
|
/bootstrap/
|
||||||
|
build/
|
||||||
5
tests/build/bootstrap.build
Normal file
5
tests/build/bootstrap.build
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
project = # Unnamed tests subproject.
|
||||||
|
|
||||||
|
using config
|
||||||
|
using test
|
||||||
|
using dist
|
||||||
16
tests/build/root.build
Normal file
16
tests/build/root.build
Normal 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
1
tests/buildfile
Normal file
@@ -0,0 +1 @@
|
|||||||
|
./: {*/ -build/}
|
||||||
Reference in New Issue
Block a user