Hello libcode-json
This commit is contained in:
commit
53255d1e23
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
|
24
.gitea/workflows/on-push.yaml
Normal file
24
.gitea/workflows/on-push.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
name: on-push
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: linux
|
||||||
|
container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest
|
||||||
|
volumes:
|
||||||
|
- /build
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Authenticate
|
||||||
|
run: |
|
||||||
|
git config unset http.https://code.helloryan.se/.extraheader
|
||||||
|
echo "${{ secrets.NETRC }}" >> ~/.netrc
|
||||||
|
- name: Initialize
|
||||||
|
run: |
|
||||||
|
bpkg create -d /build cc config.cc.coptions="-Wall -Werror"
|
||||||
|
bdep init -A /build
|
||||||
|
- name: Build
|
||||||
|
run: b
|
||||||
|
- name: Test
|
||||||
|
run: b test
|
31
.gitignore
vendored
Normal file
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.
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# libcode-json
|
||||||
|
|
||||||
|
![Build status](https://code.helloryan.se/code/libcode-json/actions/workflows/on-push.yaml/badge.svg)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
None, other than a modern C++-compiler.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for
|
||||||
|
build instructions.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Please report bugs and issues by sending an e-mail to: ryan@helloryan.se.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please send an e-mail to ryan@helloryan.se to request an account and
|
||||||
|
write-access to the libcode-json repository.
|
4
build/.gitignore
vendored
Normal file
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 = libcode-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 code/json/
|
||||||
|
}
|
||||||
|
|
||||||
|
export $out_root/code/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 @@
|
|||||||
|
./: {code/ tests/} doc{README.md} legal{LICENSE} manifest
|
||||||
|
|
||||||
|
# Don't install tests.
|
||||||
|
#
|
||||||
|
tests/: install = false
|
9
code/json/.gitignore
vendored
Normal file
9
code/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
code/json/.swp
Normal file
BIN
code/json/.swp
Normal file
Binary file not shown.
65
code/json/buildfile
Normal file
65
code/json/buildfile
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
intf_libs = # Interface dependencies.
|
||||||
|
impl_libs = # Implementation dependencies.
|
||||||
|
test_libs = # Test dependencies.
|
||||||
|
|
||||||
|
import intf_libs =+ libcode-unicode%lib{code-unicode}
|
||||||
|
|
||||||
|
./: lib{code-json}: libul{code-json}
|
||||||
|
|
||||||
|
libul{code-json}: {hxx ixx txx cxx}{** -**.test... -version} \
|
||||||
|
{hxx }{ version}
|
||||||
|
|
||||||
|
libul{code-json}: $impl_libs $intf_libs
|
||||||
|
|
||||||
|
# Unit tests.
|
||||||
|
#
|
||||||
|
exe{*.test}:
|
||||||
|
{
|
||||||
|
test = true
|
||||||
|
install = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for t: cxx{**.test...}
|
||||||
|
{
|
||||||
|
d = $directory($t)
|
||||||
|
n = $name($t)...
|
||||||
|
|
||||||
|
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n} $test_libs
|
||||||
|
$d/exe{$n}: libul{code-json}: bin.whole = false
|
||||||
|
}
|
||||||
|
|
||||||
|
hxx{version}: in{version} $src_root/manifest
|
||||||
|
{
|
||||||
|
dist = true
|
||||||
|
clean = ($src_root != $out_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build options.
|
||||||
|
#
|
||||||
|
cxx.poptions =+ "-I$out_root" "-I$src_root"
|
||||||
|
|
||||||
|
# Export options.
|
||||||
|
#
|
||||||
|
lib{code-json}:
|
||||||
|
{
|
||||||
|
cxx.export.poptions = "-I$out_root" "-I$src_root"
|
||||||
|
cxx.export.libs = $intf_libs
|
||||||
|
}
|
||||||
|
|
||||||
|
# For pre-releases use the complete version to make sure they cannot
|
||||||
|
# be used in place of another pre-release or the final version. See
|
||||||
|
# the version module for details on the version.* variable values.
|
||||||
|
#
|
||||||
|
if $version.pre_release
|
||||||
|
lib{code-json}: bin.lib.version = "-$version.project_id"
|
||||||
|
else
|
||||||
|
lib{code-json}: bin.lib.version = "-$version.major.$version.minor"
|
||||||
|
|
||||||
|
# Install into the code/json/ subdirectory of, say, /usr/include/
|
||||||
|
# recreating subdirectories.
|
||||||
|
#
|
||||||
|
{hxx ixx txx}{*}:
|
||||||
|
{
|
||||||
|
install = include/code/json/
|
||||||
|
install.subdirs = true
|
||||||
|
}
|
45
code/json/diagnostics.cxx
Normal file
45
code/json/diagnostics.cxx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#include <code/json/diagnostics.hxx>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
std::vector< std::pair< diagnostics::location, std::string > > const&
|
||||||
|
diagnostics::warnings() const
|
||||||
|
{
|
||||||
|
return warnings_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< std::pair< diagnostics::location, std::string > > const&
|
||||||
|
diagnostics::errors() const
|
||||||
|
{
|
||||||
|
return errors_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
diagnostics::warning(location loc, std::string description)
|
||||||
|
{
|
||||||
|
warnings_.emplace_back(std::make_pair(loc, description));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
diagnostics::error(location loc, std::string description)
|
||||||
|
{
|
||||||
|
errors_.emplace_back(std::make_pair(loc, description));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& o, diagnostics const& d)
|
||||||
|
{
|
||||||
|
for (auto const& j : d.warnings()) {
|
||||||
|
o << "warning: " << j.first.line << ':' << j.first.column << ": "
|
||||||
|
<< j.second << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& j : d.errors()) {
|
||||||
|
o << "error: " << j.first.line << ':' << j.first.column << ": " << j.second
|
||||||
|
<< '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json
|
43
code/json/diagnostics.hxx
Normal file
43
code/json/diagnostics.hxx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef code__json__diagnostics_hxx_
|
||||||
|
#define code__json__diagnostics_hxx_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
class diagnostics
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct location
|
||||||
|
{
|
||||||
|
std::uint32_t line;
|
||||||
|
std::uint32_t column;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector< std::pair< diagnostics::location, std::string > > const&
|
||||||
|
warnings() const;
|
||||||
|
|
||||||
|
std::vector< std::pair< diagnostics::location, std::string > > const&
|
||||||
|
errors() const;
|
||||||
|
|
||||||
|
void
|
||||||
|
warning(location loc, std::string description);
|
||||||
|
|
||||||
|
void
|
||||||
|
error(location loc, std::string description);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector< std::pair< location, std::string > > warnings_;
|
||||||
|
std::vector< std::pair< location, std::string > > errors_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& o, diagnostics const& d);
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
54
code/json/emitter.hxx
Normal file
54
code/json/emitter.hxx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#ifndef code__json__emitter_hxx_
|
||||||
|
#define code__json__emitter_hxx_
|
||||||
|
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
class emitter {
|
||||||
|
public:
|
||||||
|
explicit emitter(std::ostream& output);
|
||||||
|
|
||||||
|
std::ostream&
|
||||||
|
output();
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(variant::undefined_t const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(bool const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(long long int const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(unsigned long long int const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(long double const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(std::string const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(std::vector< variant > const& value);
|
||||||
|
|
||||||
|
void
|
||||||
|
operator()(std::map< std::string, variant > const& value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void
|
||||||
|
do_indent();
|
||||||
|
|
||||||
|
std::ostream& output_;
|
||||||
|
std::size_t indent_{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#include <code/json/emitter.ixx>
|
||||||
|
|
||||||
|
#endif
|
141
code/json/emitter.ixx
Normal file
141
code/json/emitter.ixx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
inline emitter::emitter(std::ostream& output) : output_{ output }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline std::ostream&
|
||||||
|
emitter::output()
|
||||||
|
{
|
||||||
|
return output_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(variant::undefined_t const& value)
|
||||||
|
{
|
||||||
|
output() << "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(bool const& value)
|
||||||
|
{
|
||||||
|
output() << (value ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(long long int const& value)
|
||||||
|
{
|
||||||
|
output() << std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(unsigned long long int const& value)
|
||||||
|
{
|
||||||
|
output() << std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(long double const& value)
|
||||||
|
{
|
||||||
|
output() << std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(std::string const& value)
|
||||||
|
{
|
||||||
|
output() << '\"';
|
||||||
|
|
||||||
|
for (auto const j : value) {
|
||||||
|
switch (j) {
|
||||||
|
case '\n':
|
||||||
|
output() << "\\n";
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
output() << "\\t";
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
output() << "\\";
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
output() << "\\\"";
|
||||||
|
break;
|
||||||
|
// case '\x22':
|
||||||
|
// case '\x5c':
|
||||||
|
// case '\x2f':
|
||||||
|
// case '\x62':
|
||||||
|
// case '\x66':
|
||||||
|
// case '\x6e':
|
||||||
|
// case '\x72':
|
||||||
|
// case '\x74':
|
||||||
|
default:
|
||||||
|
output() << j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output() << '\"';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(std::vector< variant > const& value)
|
||||||
|
{
|
||||||
|
if (value.empty()) {
|
||||||
|
output() << "[]";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output() << '[' << '\n';
|
||||||
|
|
||||||
|
++indent_;
|
||||||
|
|
||||||
|
bool first{ true };
|
||||||
|
for (auto const& j : value) {
|
||||||
|
if (!first)
|
||||||
|
output() << ',' << '\n';
|
||||||
|
do_indent();
|
||||||
|
first = false;
|
||||||
|
visit(*this, j.value_);
|
||||||
|
}
|
||||||
|
output() << '\n';
|
||||||
|
--indent_;
|
||||||
|
|
||||||
|
do_indent();
|
||||||
|
output() << ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::operator()(std::map< std::string, variant > const& value)
|
||||||
|
{
|
||||||
|
if (value.empty()) {
|
||||||
|
output() << "{}";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output() << "{\n";
|
||||||
|
|
||||||
|
++indent_;
|
||||||
|
|
||||||
|
bool first{ true };
|
||||||
|
for (auto const& j : value) {
|
||||||
|
if (!first)
|
||||||
|
output() << ",\n";
|
||||||
|
first = false;
|
||||||
|
do_indent();
|
||||||
|
(*this)(j.first);
|
||||||
|
output() << ':' << ' ';
|
||||||
|
visit(*this, j.second.value_);
|
||||||
|
}
|
||||||
|
output() << '\n';
|
||||||
|
--indent_;
|
||||||
|
|
||||||
|
do_indent();
|
||||||
|
output() << '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
emitter::do_indent()
|
||||||
|
{
|
||||||
|
std::size_t i{ indent_ * 2 };
|
||||||
|
while (i-- > 0)
|
||||||
|
output() << ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json
|
115
code/json/emitter.test.cxx
Normal file
115
code/json/emitter.test.cxx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#include <code/json/emitter.hxx>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||||
|
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("undefined")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(code::json::variant::undefined_t{});
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "null");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: true")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(true);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: false")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(false);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("signed number")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(-10LL);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "-10");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("unsigned number")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(100LL);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "100");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("real number")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(1.2L);
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "1.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("string")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(std::string{ "hello" });
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "\"hello\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: []")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
emitter(std::vector< code::json::variant >{});
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: [null]")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
|
||||||
|
emitter(std::vector< code::json::variant >{ code::json::variant{} });
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "[\n null\n]");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: {}")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
|
||||||
|
emitter(std::map< std::string, code::json::variant >{});
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: {...}")
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
code::json::emitter emitter{ str };
|
||||||
|
|
||||||
|
emitter(std::map< std::string, code::json::variant >{ { "", {} } });
|
||||||
|
|
||||||
|
TEST_EQUAL(str.str(), "{\n \"\": null\n}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
43
code/json/exception.cxx
Normal file
43
code/json/exception.cxx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include <code/json/exception.hxx>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
invalid_json::invalid_json(diagnostics::location const& loc,
|
||||||
|
std::string const& what)
|
||||||
|
: std::runtime_error{ make_error_string(loc, what) }, location_{ loc }
|
||||||
|
{}
|
||||||
|
|
||||||
|
diagnostics::location const&
|
||||||
|
invalid_json::location() const
|
||||||
|
{
|
||||||
|
return location_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
invalid_json::make_error_string(diagnostics::location const& loc,
|
||||||
|
std::string const& what)
|
||||||
|
{
|
||||||
|
std::ostringstream str;
|
||||||
|
str << loc.column << " " << loc.line << ": " << what;
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
invalid_type::invalid_type() : std::runtime_error{ "invalid type" }
|
||||||
|
{}
|
||||||
|
|
||||||
|
invalid_syntax::invalid_syntax() : std::runtime_error{ "invalid syntax" }
|
||||||
|
{}
|
||||||
|
|
||||||
|
unexpected_type::unexpected_type()
|
||||||
|
: std::runtime_error{ "unexpected JSON type" }
|
||||||
|
{}
|
||||||
|
|
||||||
|
invalid_object_key::invalid_object_key(std::string const& key)
|
||||||
|
: std::runtime_error{ "invalid key '" + key + "'" }
|
||||||
|
{}
|
||||||
|
|
||||||
|
invalid_array_index::invalid_array_index(std::string const& index)
|
||||||
|
: std::runtime_error{ "invalid key '" + index + "'" }
|
||||||
|
{}
|
||||||
|
|
||||||
|
} // namespace code::json
|
59
code/json/exception.hxx
Normal file
59
code/json/exception.hxx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef code__json__exception_hxx_
|
||||||
|
#define code__json__exception_hxx_
|
||||||
|
|
||||||
|
#include <code/json/diagnostics.hxx>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
/// Exception-class used to indicated invalid JSON.
|
||||||
|
class invalid_json : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
invalid_json(diagnostics::location const& loc, std::string const& what);
|
||||||
|
|
||||||
|
diagnostics::location const&
|
||||||
|
location() const;
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
make_error_string(diagnostics::location const& loc, std::string const& what);
|
||||||
|
|
||||||
|
private:
|
||||||
|
diagnostics::location location_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Exception-class used to indicate an invalid type.
|
||||||
|
class invalid_type : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
invalid_type();
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Exception class used to indicate invalid JSON pointer syntax.
|
||||||
|
class invalid_syntax : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
invalid_syntax();
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Exception class used to indicate an unexpected JSON type.
|
||||||
|
class unexpected_type : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
unexpected_type();
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Exception-class used to indicate an object key.
|
||||||
|
class invalid_object_key : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit invalid_object_key(std::string const& key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Exception-class used to indicate an invalid array index.
|
||||||
|
class invalid_array_index : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit invalid_array_index(std::string const& index);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
245
code/json/marshaling/mapping.hxx
Normal file
245
code/json/marshaling/mapping.hxx
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
#ifndef json__marshaling__mapping_hxx_
|
||||||
|
#define json__marshaling__mapping_hxx_
|
||||||
|
|
||||||
|
#include <code/json/optional.hxx>
|
||||||
|
#include <code/json/pointer.hxx>
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <code/json/marshaling/marshaling-traits.hxx>
|
||||||
|
#include <code/json/marshaling/traits.hxx>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
class member_mapping {
|
||||||
|
public:
|
||||||
|
using getter_type = std::function< variant(T const&, marshaling_context*) >;
|
||||||
|
using setter_type = std::function< void(T&, variant const&, marshaling_context*) >;
|
||||||
|
|
||||||
|
member_mapping(std::string key,
|
||||||
|
bool optional,
|
||||||
|
getter_type getter,
|
||||||
|
setter_type setter)
|
||||||
|
: key_{ std::move(key) },
|
||||||
|
optional_{ optional },
|
||||||
|
getter_{ std::move(getter) },
|
||||||
|
setter_{ std::move(setter) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
member_mapping(pointer p,
|
||||||
|
bool optional,
|
||||||
|
getter_type getter,
|
||||||
|
setter_type setter)
|
||||||
|
: key_{ std::move(p) },
|
||||||
|
optional_{ optional },
|
||||||
|
getter_{ std::move(getter) },
|
||||||
|
setter_{ std::move(setter) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
template< typename U >
|
||||||
|
member_mapping(member_mapping< U > const& other)
|
||||||
|
: key_{other.key_},
|
||||||
|
optional_{other.optional_},
|
||||||
|
getter_{other.getter_},
|
||||||
|
setter_{other.setter_}
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::variant<std::string, pointer> const&
|
||||||
|
key() const
|
||||||
|
{
|
||||||
|
return key_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
optional() const
|
||||||
|
{
|
||||||
|
return optional_;
|
||||||
|
}
|
||||||
|
|
||||||
|
variant
|
||||||
|
get(T const& instance, marshaling_context* context) const
|
||||||
|
{
|
||||||
|
return getter_(instance, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
set(T& instance, variant const& value, marshaling_context* context) const
|
||||||
|
{
|
||||||
|
setter_(instance, value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template< typename U >
|
||||||
|
friend class member_mapping;
|
||||||
|
|
||||||
|
std::variant<std::string, pointer> key_;
|
||||||
|
bool optional_;
|
||||||
|
getter_type getter_;
|
||||||
|
setter_type setter_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T, typename M >
|
||||||
|
member_mapping< T >
|
||||||
|
member(std::string key, M T::*member)
|
||||||
|
{
|
||||||
|
return member_mapping< T >{
|
||||||
|
std::move(key),
|
||||||
|
is_optional_v<M>,
|
||||||
|
[member](T const& instance, marshaling_context* context) -> json::variant
|
||||||
|
{
|
||||||
|
return marshaling_traits< M >::marshal(instance.*member, context);
|
||||||
|
},
|
||||||
|
[member](T& instance, json::variant const& v, marshaling_context* context)
|
||||||
|
{
|
||||||
|
instance.*member = marshaling_traits< M >::unmarshal(v, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename M >
|
||||||
|
member_mapping< T >
|
||||||
|
member(pointer p, M T::*member)
|
||||||
|
{
|
||||||
|
return member_mapping< T >{
|
||||||
|
std::move(p),
|
||||||
|
is_optional_v<M>,
|
||||||
|
[member](T const& instance, marshaling_context* context) -> json::variant
|
||||||
|
{
|
||||||
|
return marshaling_traits< M >::marshal(instance.*member, context);
|
||||||
|
},
|
||||||
|
[member](T& instance, json::variant const& v, marshaling_context* context)
|
||||||
|
{
|
||||||
|
instance.*member = marshaling_traits< M >::unmarshal(v, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename Accessor >
|
||||||
|
member_mapping< T >
|
||||||
|
accessor(std::string key, Accessor access)
|
||||||
|
{
|
||||||
|
return member_mapping< T >{
|
||||||
|
std::move(key),
|
||||||
|
is_optional_v<std::decay_t<decltype(access(std::declval<T>()))>>,
|
||||||
|
[access](T const& instance, marshaling_context* context) -> json::variant
|
||||||
|
{
|
||||||
|
using M = std::decay_t<decltype(access(instance))>;
|
||||||
|
return marshaling_traits< M >::marshal(access(instance), context);
|
||||||
|
},
|
||||||
|
[access](T& instance, json::variant const& v, marshaling_context* context)
|
||||||
|
{
|
||||||
|
using M = std::decay_t<decltype(access(instance))>;
|
||||||
|
access(instance) = marshaling_traits< M >::unmarshal(v, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename Accessor >
|
||||||
|
member_mapping< T >
|
||||||
|
accessor(pointer p, Accessor access)
|
||||||
|
{
|
||||||
|
return member_mapping< T >{
|
||||||
|
std::move(p),
|
||||||
|
is_optional_v<std::decay_t<decltype(access(std::declval<T>()))>>,
|
||||||
|
[access](T const& instance, marshaling_context* context) -> json::variant
|
||||||
|
{
|
||||||
|
using M = std::decay_t<decltype(access(instance))>;
|
||||||
|
return marshaling_traits< M >::marshal(access(instance), context);
|
||||||
|
},
|
||||||
|
[access](T& instance, json::variant const& v, marshaling_context* context)
|
||||||
|
{
|
||||||
|
using M = std::decay_t<decltype(access(instance))>;
|
||||||
|
access(instance) = marshaling_traits< M >::unmarshal(v, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename V >
|
||||||
|
member_mapping< T >
|
||||||
|
static_value(std::string key, V value)
|
||||||
|
{
|
||||||
|
return member_mapping< T >{
|
||||||
|
std::move(key),
|
||||||
|
true,
|
||||||
|
[value](T const& instance, marshaling_context* context) -> json::variant {
|
||||||
|
return marshaling_traits< V >::marshal(value, context);
|
||||||
|
},
|
||||||
|
[](T&, json::variant const&, marshaling_context*) {
|
||||||
|
// No-op.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename V >
|
||||||
|
member_mapping< T >
|
||||||
|
static_value(pointer p, V value)
|
||||||
|
{
|
||||||
|
return member_mapping< T >{
|
||||||
|
std::move(p),
|
||||||
|
true,
|
||||||
|
[value](T const& instance, marshaling_context* context) -> json::variant {
|
||||||
|
return marshaling_traits< V >::marshal(value, context);
|
||||||
|
},
|
||||||
|
[](T&, json::variant const&, marshaling_context*) {
|
||||||
|
// No-op.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
class mapping {
|
||||||
|
public:
|
||||||
|
using container_type = std::vector< member_mapping< T > >;
|
||||||
|
using const_iterator = typename container_type::const_iterator;
|
||||||
|
|
||||||
|
mapping(container_type mappings)
|
||||||
|
: mappings_{std::move(mappings)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
mapping(std::initializer_list< member_mapping< T > > init)
|
||||||
|
{
|
||||||
|
for (auto const& j : init)
|
||||||
|
mappings_.emplace_back(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator
|
||||||
|
begin() const
|
||||||
|
{
|
||||||
|
return mappings_.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator
|
||||||
|
end() const
|
||||||
|
{
|
||||||
|
return mappings_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
container_type mappings_;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename... Bases>
|
||||||
|
mapping<T>
|
||||||
|
map(std::initializer_list<member_mapping<T>> init)
|
||||||
|
{
|
||||||
|
typename mapping<T>::container_type m{init};
|
||||||
|
|
||||||
|
auto inherit = [&](auto&& base)
|
||||||
|
{
|
||||||
|
for (auto const& j : base) {
|
||||||
|
m.emplace_back(j);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
((inherit(Bases::json())), ...);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
||||||
|
|
||||||
|
#endif
|
8
code/json/marshaling/marshaling-context.cxx
Normal file
8
code/json/marshaling/marshaling-context.cxx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include <code/json/marshaling/marshaling-context.hxx>
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
marshaling_context::~marshaling_context()
|
||||||
|
{}
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
13
code/json/marshaling/marshaling-context.hxx
Normal file
13
code/json/marshaling/marshaling-context.hxx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef code__json__marshaling__marshaling_context_hxx_
|
||||||
|
#define code__json__marshaling__marshaling_context_hxx_
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
class marshaling_context {
|
||||||
|
protected:
|
||||||
|
virtual ~marshaling_context();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
||||||
|
|
||||||
|
#endif
|
626
code/json/marshaling/marshaling-traits.hxx
Normal file
626
code/json/marshaling/marshaling-traits.hxx
Normal file
@ -0,0 +1,626 @@
|
|||||||
|
#ifndef code__json__marshaling__marshaling_traits_hxx_
|
||||||
|
#define code__json__marshaling__marshaling_traits_hxx_
|
||||||
|
|
||||||
|
#include <code/json/optional.hxx>
|
||||||
|
#include <code/json/pointer.hxx>
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <code/json/marshaling/marshaling-context.hxx>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <deque>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream> // TODO: Remove.
|
||||||
|
#include <list>
|
||||||
|
#include <locale>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct marshaling_traits {
|
||||||
|
using model_type = T;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(model_type const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
variant v{std::map<std::string, variant>{}};
|
||||||
|
|
||||||
|
// TODO: Handle mappings with pointers.
|
||||||
|
for (auto const& mapping : model_type::json()) {
|
||||||
|
if (std::holds_alternative<pointer>(mapping.key())) {
|
||||||
|
auto ptr = std::get<pointer>(mapping.key());
|
||||||
|
ptr.write(v, mapping.get(model, context));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
v.set(std::get<std::string>(mapping.key()), mapping.get(model, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static model_type
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_default_constructible_v< model_type >) {
|
||||||
|
// TODO: Change exception type.
|
||||||
|
if (!value.is_object())
|
||||||
|
throw std::runtime_error{ "cannot unmarshal non-object value" };
|
||||||
|
|
||||||
|
model_type model;
|
||||||
|
for (auto const& mapping : model_type::json()) {
|
||||||
|
if (std::holds_alternative<pointer>(mapping.key())) {
|
||||||
|
auto ptr = std::get<pointer>(mapping.key());
|
||||||
|
auto v = ptr.read(value);
|
||||||
|
|
||||||
|
if (!v && !mapping.optional())
|
||||||
|
// FIXME: Add ptr to exception.
|
||||||
|
throw std::runtime_error{ "missing field '" + to_string(ptr) + "'" };
|
||||||
|
else
|
||||||
|
mapping.set(model, *v, context);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& key = std::get<std::string>(mapping.key());
|
||||||
|
|
||||||
|
if (!value.contains(key)) {
|
||||||
|
if (!mapping.optional())
|
||||||
|
// TODO: Change exception type.
|
||||||
|
throw std::runtime_error{ "missing field '" + key + "'" };
|
||||||
|
|
||||||
|
continue; // Try next key.
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping.set(model, value.get(key), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error{ "this shouldn't compile" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct marshaling_traits< optional< T > > {
|
||||||
|
using model_type = optional< T >;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(optional< T > const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (model)
|
||||||
|
return marshaling_traits< T >::marshal(*model, context);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static optional< T >
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (value.is_undefined())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return marshaling_traits< T >::unmarshal(value, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename... T >
|
||||||
|
struct marshaling_traits< std::variant< T... > > {
|
||||||
|
using model_type = std::variant< T... >;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::variant< T... > const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
variant result;
|
||||||
|
|
||||||
|
auto visitor = [&result, context](const auto& obj)
|
||||||
|
{
|
||||||
|
using type = std::decay_t<decltype(obj)>;
|
||||||
|
result = marshaling_traits<type>::marshal(obj, context);
|
||||||
|
result.set("$type", std::decay_t<decltype(obj)>::type_identifier);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::visit(visitor, model);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Current, typename... Next>
|
||||||
|
struct unmarshaler
|
||||||
|
{
|
||||||
|
static
|
||||||
|
std::variant<T...>
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (!value.contains("$type"))
|
||||||
|
throw std::invalid_argument{"variant missing '$type' identifier"};
|
||||||
|
|
||||||
|
if (value.get("$type").get_string() == Current::type_identifier)
|
||||||
|
return marshaling_traits< Current >::unmarshal(value, context);
|
||||||
|
|
||||||
|
if constexpr (sizeof...(Next) > 0)
|
||||||
|
return unmarshaler<Next...>::unmarshal(value, context);
|
||||||
|
|
||||||
|
throw std::invalid_argument{"couldn't unmarshal value"};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::variant< T... >
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
return unmarshaler<T...>::unmarshal(value, context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< bool > {
|
||||||
|
using model_type = bool;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(bool model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_boolean())
|
||||||
|
throw std::runtime_error{ "not a boolean" };
|
||||||
|
|
||||||
|
return value.get_boolean();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< short int > {
|
||||||
|
using model_type = short int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(short int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static short int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< short int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< int > {
|
||||||
|
using model_type = int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< long int > {
|
||||||
|
using model_type = long int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(long int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< long int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< long long int > {
|
||||||
|
using model_type = long long int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(long long int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long long int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< long long int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< unsigned short int > {
|
||||||
|
using model_type = unsigned short int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(unsigned short int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned short int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< unsigned short int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< unsigned int > {
|
||||||
|
using model_type = unsigned int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(unsigned int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< unsigned int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< unsigned long int > {
|
||||||
|
using model_type = unsigned long int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(unsigned long int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< unsigned long int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// unsigned long long int
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< unsigned long long int > {
|
||||||
|
using model_type = unsigned long long int;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(unsigned long long int model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long long int
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< unsigned long long int >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< float > {
|
||||||
|
using model_type = float;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(float model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< float >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< double > {
|
||||||
|
using model_type = double;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(double model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< double >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< long double > {
|
||||||
|
using model_type = long double;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(long double model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long double
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_number())
|
||||||
|
throw std::runtime_error{ "not a number" };
|
||||||
|
|
||||||
|
return value.get_number< long double >();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< std::string > {
|
||||||
|
using model_type = std::string;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::string const& model, marshaling_context*)
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
if (!value.is_string())
|
||||||
|
throw std::runtime_error{ "not a string" };
|
||||||
|
|
||||||
|
return value.get_string();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct marshaling_traits< std::vector< T > > {
|
||||||
|
using model_type = std::vector< T >;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::vector< T > const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
std::vector< variant > a;
|
||||||
|
|
||||||
|
for (auto const& j : model)
|
||||||
|
a.emplace_back(marshaling_traits< T >::marshal(j, context));
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector< T >
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (!value.is_array())
|
||||||
|
throw std::runtime_error{ "not an array" };
|
||||||
|
|
||||||
|
std::vector< T > model;
|
||||||
|
|
||||||
|
for (auto const& j : value)
|
||||||
|
model.emplace_back(marshaling_traits< T >::unmarshal(j, context));
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct marshaling_traits< std::list< T > > {
|
||||||
|
using model_type = std::list< T >;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::list< T > const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
std::vector< variant > a;
|
||||||
|
|
||||||
|
for (auto const& j : model)
|
||||||
|
a.emplace_back(marshaling_traits< T >::marshal(j, context));
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::list< T >
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (!value.is_array())
|
||||||
|
throw std::runtime_error{ "not an array" };
|
||||||
|
|
||||||
|
std::list< T > model;
|
||||||
|
|
||||||
|
for (auto const& j : value)
|
||||||
|
model.emplace_back(marshaling_traits< T >::unmarshal(j, context));
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct marshaling_traits< std::deque< T > > {
|
||||||
|
using model_type = std::deque< T >;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::deque< T > const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
std::vector< variant > a;
|
||||||
|
|
||||||
|
for (auto const& j : model)
|
||||||
|
a.emplace_back(marshaling_traits< T >::marshal(j, context));
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::deque< T >
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (!value.is_array())
|
||||||
|
throw std::runtime_error{ "not an array" };
|
||||||
|
|
||||||
|
std::deque< T > model;
|
||||||
|
|
||||||
|
for (auto const& j : value)
|
||||||
|
model.emplace_back(marshaling_traits< T >::unmarshal(j, context));
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct marshaling_traits< std::set< T > > {
|
||||||
|
using model_type = std::set< T >;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::set< T > const& model, marshaling_context* context)
|
||||||
|
{
|
||||||
|
std::vector< variant > a;
|
||||||
|
|
||||||
|
for (auto const& j : model)
|
||||||
|
a.emplace_back(marshaling_traits< T >::marshal(j, context));
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::set< T >
|
||||||
|
unmarshal(variant const& value, marshaling_context* context)
|
||||||
|
{
|
||||||
|
if (!value.is_array())
|
||||||
|
throw std::runtime_error{ "not an array" };
|
||||||
|
|
||||||
|
std::set< T > model;
|
||||||
|
|
||||||
|
for (auto const& j : value)
|
||||||
|
model.emplace(marshaling_traits< T >::unmarshal(j, context));
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct marshaling_traits< std::chrono::system_clock::time_point > {
|
||||||
|
using model_type = std::string;
|
||||||
|
|
||||||
|
static variant
|
||||||
|
marshal(std::chrono::system_clock::time_point const& model,
|
||||||
|
marshaling_context*)
|
||||||
|
{
|
||||||
|
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
|
||||||
|
|
||||||
|
std::time_t now_c = std::chrono::system_clock::to_time_t(model);
|
||||||
|
|
||||||
|
struct std::tm tm_buf;
|
||||||
|
|
||||||
|
std::stringstream str;
|
||||||
|
str.imbue(std::locale{});
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
::gmtime_s(&tm_buf, &now_c); // Stupid Microsoft.
|
||||||
|
str << std::put_time(&tm_buf, time_format);
|
||||||
|
#else
|
||||||
|
::gmtime_r(&now_c, &tm_buf);
|
||||||
|
str << std::put_time(&tm_buf, time_format);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::chrono::system_clock::time_point
|
||||||
|
unmarshal(variant const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
static constexpr const char time_format[] = "%a, %d %b %Y %H:%M:%S GMT";
|
||||||
|
|
||||||
|
if (!value.is_string())
|
||||||
|
throw std::runtime_error{ "not a string" };
|
||||||
|
|
||||||
|
std::tm tm{};
|
||||||
|
|
||||||
|
std::istringstream str{ value.get_string() };
|
||||||
|
str.imbue(std::locale{});
|
||||||
|
|
||||||
|
str >> std::get_time(&tm, time_format);
|
||||||
|
|
||||||
|
if (str.fail())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
auto localtime = _mkgmtime(&tm);
|
||||||
|
return std::chrono::system_clock::from_time_t(localtime);
|
||||||
|
#else
|
||||||
|
auto localtime = timegm(&tm);
|
||||||
|
return std::chrono::system_clock::from_time_t(localtime);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename E,
|
||||||
|
std::string(&to_string)(E const&),
|
||||||
|
E(&from_string)(std::string const&)>
|
||||||
|
struct enum_mapping
|
||||||
|
{
|
||||||
|
static
|
||||||
|
variant
|
||||||
|
marshal(E const& value, marshaling_context*)
|
||||||
|
{
|
||||||
|
return to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
E
|
||||||
|
unmarshal(variant const& v, marshaling_context*)
|
||||||
|
{
|
||||||
|
return from_string(v.get_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
||||||
|
|
||||||
|
#endif
|
146
code/json/marshaling/marshaling-traits.test.cxx
Normal file
146
code/json/marshaling/marshaling-traits.test.cxx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#include <code/json/optional.hxx>
|
||||||
|
|
||||||
|
#include <code/json/marshaling/marshaling-traits.hxx>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <iostream>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||||
|
#define TEST_TRUE(x) if (!(x)) return __LINE__;
|
||||||
|
#define TEST_FALSE(x) if ((x)) return __LINE__;
|
||||||
|
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("optional<int>{}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >;
|
||||||
|
|
||||||
|
std::optional< int > model;
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("optional<int>{0}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< code::json::optional< int > >;
|
||||||
|
|
||||||
|
std::optional< int > model{ 0 };
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model, nullptr);
|
||||||
|
|
||||||
|
TEST_FALSE(j.is_undefined());
|
||||||
|
TEST_TRUE(j.is_number());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("string")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::string >;
|
||||||
|
|
||||||
|
std::string model{ "hello, world" };
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_string());
|
||||||
|
TEST_EQUAL(j.get_string(), "hello, world");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("vector<int>{}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >;
|
||||||
|
|
||||||
|
std::vector< int > model1;
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model1, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_array());
|
||||||
|
|
||||||
|
auto model2 = traits_type::unmarshal(j, nullptr);
|
||||||
|
|
||||||
|
TEST_EQUAL(model1, model2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("vector<int>{...}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::vector< int > >;
|
||||||
|
|
||||||
|
std::vector< int > model1{ 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model1, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_array());
|
||||||
|
|
||||||
|
auto model2 = traits_type::unmarshal(j, nullptr);
|
||||||
|
|
||||||
|
TEST_EQUAL(model1, model2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("list<int>{}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >;
|
||||||
|
|
||||||
|
std::list< int > model1;
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model1, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_array());
|
||||||
|
|
||||||
|
auto model2 = traits_type::unmarshal(j, nullptr);
|
||||||
|
|
||||||
|
TEST_EQUAL(model1, model2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("list<int>{...}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::list< int > >;
|
||||||
|
|
||||||
|
std::list< int > model1{ 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model1, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_array());
|
||||||
|
|
||||||
|
auto model2 = traits_type::unmarshal(j, nullptr);
|
||||||
|
|
||||||
|
TEST_EQUAL(model1, model2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("deque<int>{}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >;
|
||||||
|
|
||||||
|
std::deque< int > model1;
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model1, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_array());
|
||||||
|
|
||||||
|
auto model2 = traits_type::unmarshal(j, nullptr);
|
||||||
|
|
||||||
|
TEST_EQUAL(model1, model2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("deque<int>{...}")
|
||||||
|
{
|
||||||
|
using traits_type = code::json::marshaling::marshaling_traits< std::deque< int > >;
|
||||||
|
|
||||||
|
std::deque< int > model1{ 1, 2, 3, 4 };
|
||||||
|
|
||||||
|
auto j = traits_type::marshal(model1, nullptr);
|
||||||
|
|
||||||
|
TEST_TRUE(j.is_array());
|
||||||
|
|
||||||
|
auto model2 = traits_type::unmarshal(j, nullptr);
|
||||||
|
|
||||||
|
TEST_EQUAL(model1, model2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
29
code/json/marshaling/marshaling.hxx
Normal file
29
code/json/marshaling/marshaling.hxx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#ifndef code__json__marshaling__marshaling_hxx_
|
||||||
|
#define code__json__marshaling__marshaling_hxx_
|
||||||
|
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <code/json/marshaling/marshaling-context.hxx>
|
||||||
|
#include <code/json/marshaling/marshaling-traits.hxx>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
variant
|
||||||
|
marshal(T const& model, marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
return marshaling_traits< T >::marshal(model, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
typename marshaling_traits< T >::model_type
|
||||||
|
unmarshal(variant const& value, marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
return marshaling_traits< T >::unmarshal(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
||||||
|
|
||||||
|
#endif
|
91
code/json/marshaling/serialize.hxx
Normal file
91
code/json/marshaling/serialize.hxx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#ifndef code__json__marshaling__serialize_hxx_
|
||||||
|
#define code__json__marshaling__serialize_hxx_
|
||||||
|
|
||||||
|
#include <code/json/marshaling/marshaling-context.hxx>
|
||||||
|
#include <code/json/marshaling/marshaling.hxx>
|
||||||
|
|
||||||
|
#include <code/json/read.hxx>
|
||||||
|
#include <code/json/write.hxx>
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
void
|
||||||
|
serialize(std::ostream& o, T const& model)
|
||||||
|
{
|
||||||
|
write(o, marshal(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
void
|
||||||
|
serialize(std::ostream&& o, T const& model)
|
||||||
|
{
|
||||||
|
serialize(o, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
std::string
|
||||||
|
serialize(T const& model)
|
||||||
|
{
|
||||||
|
std::ostringstream str;
|
||||||
|
serialize(str, model);
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
T
|
||||||
|
deserialize(diagnostics& d,
|
||||||
|
std::istream& i,
|
||||||
|
marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
return unmarshal< T >(read(d, i), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
T
|
||||||
|
deserialize(std::istream& i, marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
return unmarshal< T >(read(i), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
T
|
||||||
|
deserialize(diagnostics& d,
|
||||||
|
std::istream&& i,
|
||||||
|
marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
return deserialize< T >(d, i, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
T
|
||||||
|
deserialize(std::istream&& i, marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
return deserialize< T >(i, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
T
|
||||||
|
deserialize(diagnostics& d,
|
||||||
|
std::string const& str,
|
||||||
|
marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
// TODO use std::string overload of read
|
||||||
|
return deserialize< T >(
|
||||||
|
d, std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
T
|
||||||
|
deserialize(std::string const& str, marshaling_context* context = nullptr)
|
||||||
|
{
|
||||||
|
// TODO use std::string overload of read
|
||||||
|
return deserialize< T >(
|
||||||
|
std::istringstream{ str, std::ios::in | std::ios::binary }, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
||||||
|
|
||||||
|
#endif
|
60
code/json/marshaling/serialize.test.cxx
Normal file
60
code/json/marshaling/serialize.test.cxx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include <code/json/marshaling/mapping.hxx>
|
||||||
|
#include <code/json/marshaling/serialize.hxx>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||||
|
#define TEST_TRUE(x) if (!(x)) return __LINE__;
|
||||||
|
#define TEST_FALSE(x) if ((x)) return __LINE__;
|
||||||
|
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||||
|
|
||||||
|
struct person_name {
|
||||||
|
std::string first;
|
||||||
|
std::string last;
|
||||||
|
|
||||||
|
static code::json::marshaling::mapping< person_name > const&
|
||||||
|
json()
|
||||||
|
{
|
||||||
|
static code::json::marshaling::mapping< person_name > const mapping{
|
||||||
|
code::json::marshaling::member("first", &person_name::first),
|
||||||
|
code::json::marshaling::member("last", &person_name::last)
|
||||||
|
};
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct person {
|
||||||
|
person_name name;
|
||||||
|
int age;
|
||||||
|
|
||||||
|
static code::json::marshaling::mapping< person > const&
|
||||||
|
json()
|
||||||
|
{
|
||||||
|
static code::json::marshaling::mapping< person > const mapping{
|
||||||
|
code::json::marshaling::member("person", &person::name),
|
||||||
|
code::json::marshaling::member("age", &person::age)
|
||||||
|
};
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("round trip")
|
||||||
|
{
|
||||||
|
using namespace code::json::marshaling;
|
||||||
|
|
||||||
|
person p1{ { "Jane", "Doe" }, 37 };
|
||||||
|
|
||||||
|
auto p2 = deserialize< person >(serialize(p1));
|
||||||
|
|
||||||
|
TEST_EQUAL(p1.name.first, p2.name.first);
|
||||||
|
TEST_EQUAL(p1.name.last, p2.name.last);
|
||||||
|
TEST_EQUAL(p1.age, p2.age);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
72
code/json/marshaling/traits.hxx
Normal file
72
code/json/marshaling/traits.hxx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#ifndef code__json__marshaling__traits_hxx_
|
||||||
|
#define code__json__marshaling__traits_hxx_
|
||||||
|
|
||||||
|
#include <code/json/optional.hxx>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace code::json::marshaling {
|
||||||
|
|
||||||
|
// is_optional<T>.
|
||||||
|
//
|
||||||
|
|
||||||
|
template< typename >
|
||||||
|
struct is_optional : std::false_type {
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct is_optional< optional< T > > : std::true_type {
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
constexpr bool is_optional_v = is_optional< T >::value;
|
||||||
|
|
||||||
|
// function_traits<T>.
|
||||||
|
//
|
||||||
|
|
||||||
|
template< typename, typename = std::void_t< > >
|
||||||
|
struct function_traits;
|
||||||
|
|
||||||
|
template< typename Ret, typename... Args >
|
||||||
|
struct function_traits< Ret(Args...) > {
|
||||||
|
static constexpr std::size_t arity = sizeof...(Args);
|
||||||
|
using return_type = std::decay_t< Ret >;
|
||||||
|
using argument_tuple = std::tuple< std::decay_t< Args >... >;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Ret, typename Class, typename... Args >
|
||||||
|
struct function_traits< Ret (Class::*)(Args...) > {
|
||||||
|
static constexpr std::size_t arity = sizeof...(Args);
|
||||||
|
using return_type = std::decay_t< Ret >;
|
||||||
|
using class_type = Class;
|
||||||
|
using argument_tuple = std::tuple< std::decay_t< Args >... >;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Ret, typename Class, typename... Args >
|
||||||
|
struct function_traits< Ret (Class::*)(Args...) const > {
|
||||||
|
static constexpr std::size_t arity = sizeof...(Args);
|
||||||
|
using return_type = std::decay_t< Ret >;
|
||||||
|
using class_type = Class;
|
||||||
|
using argument_tuple = std::tuple< std::decay_t< Args >... >;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Ret, typename... Args >
|
||||||
|
struct function_traits< Ret(*)(Args...) > {
|
||||||
|
static constexpr std::size_t arity = sizeof...(Args);
|
||||||
|
using return_type = std::decay_t< Ret >;
|
||||||
|
using argument_tuple = std::tuple< std::decay_t< Args >... >;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Callable >
|
||||||
|
struct function_traits<
|
||||||
|
Callable,
|
||||||
|
std::void_t<
|
||||||
|
decltype(&Callable::operator())
|
||||||
|
>
|
||||||
|
>
|
||||||
|
: function_traits<decltype(&Callable::operator())> {
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json::marshaling
|
||||||
|
|
||||||
|
#endif
|
30
code/json/optional.hxx
Normal file
30
code/json/optional.hxx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef code__json__optional_hxx_
|
||||||
|
#define code__json__optional_hxx_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file will be REMOVED once <optional> is widely available.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(__has_include)
|
||||||
|
# error "__has_include not supported"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
# include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
# include <experimental/optional>
|
||||||
|
#else
|
||||||
|
# error "No support for <optional>"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
using std::optional;
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
using std::experimental::optional;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
758
code/json/parser.hxx
Normal file
758
code/json/parser.hxx
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
#ifndef code__json__parser_hxx_
|
||||||
|
#define code__json__parser_hxx_
|
||||||
|
|
||||||
|
#include <code/json/diagnostics.hxx>
|
||||||
|
#include <code/json/exception.hxx>
|
||||||
|
#include <code/json/optional.hxx>
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <code/unicode/decoding.hxx>
|
||||||
|
#include <code/unicode/encoding.hxx>
|
||||||
|
|
||||||
|
#include <iostream> // TODO remove
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
class variant;
|
||||||
|
|
||||||
|
class parser {
|
||||||
|
public:
|
||||||
|
parser() = default;
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
optional< variant >
|
||||||
|
try_parse(diagnostics& d, InputIterator& first, InputIterator last)
|
||||||
|
{
|
||||||
|
auto v = parse(d, first, last);
|
||||||
|
|
||||||
|
if (d.errors().size() > 0)
|
||||||
|
return variant::undefined;
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
optional< variant >
|
||||||
|
try_parse(diagnostics& d, InputIterator&& first, InputIterator last)
|
||||||
|
{
|
||||||
|
return try_parse(d, first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
parse(diagnostics& d, InputIterator& first, InputIterator last)
|
||||||
|
{
|
||||||
|
skip_whitespace(first, last);
|
||||||
|
|
||||||
|
auto v = try_parse_any(d, first, last);
|
||||||
|
|
||||||
|
if (d.errors().size() > 0)
|
||||||
|
return variant::undefined;
|
||||||
|
|
||||||
|
skip_whitespace(first, last);
|
||||||
|
|
||||||
|
if (first != last) {
|
||||||
|
d.error(location(), "unexpected trailing data");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
parse(diagnostics& d, InputIterator&& first, InputIterator last)
|
||||||
|
{
|
||||||
|
return parse(d, first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_undefined(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
if (*i != 'n') {
|
||||||
|
d.error(location(), "expected 'n'");
|
||||||
|
return { loc, variant::undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 'u') {
|
||||||
|
d.error(location(), "expected 'u'");
|
||||||
|
return { loc, variant::undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 'l') {
|
||||||
|
d.error(location(), "expected 'l'");
|
||||||
|
return { loc, variant::undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 'l') {
|
||||||
|
d.error(location(), "expected 'l'");
|
||||||
|
return { loc, variant::undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
return { std::move(loc), variant::undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_array(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
if (*i != '[') {
|
||||||
|
d.error(location(), "expected '['");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last); // skip '['
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i == ']') {
|
||||||
|
advance(i, last);
|
||||||
|
return variant{ std::move(loc), std::vector< variant >{} };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< variant > a;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
auto v = try_parse_any(d, i, last);
|
||||||
|
|
||||||
|
if (d.errors().size() > 0)
|
||||||
|
return variant::undefined;
|
||||||
|
|
||||||
|
a.emplace_back(std::move(v));
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (',' != *i)
|
||||||
|
break;
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != ']') {
|
||||||
|
d.error(location(), "expected ']'");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
return variant{ std::move(loc), std::move(a) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_boolean(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
if (*i == 't') {
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != 'r') {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last); // skip 'r'
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != 'u') {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last); // skip 'u'
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != 'e') {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last); // skip 'e'
|
||||||
|
|
||||||
|
return variant{ std::move(loc), true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != 'f')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 'a')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 'l')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 's')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != 'e')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
return variant{ std::move(loc), false };
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_number(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
std::string buf;
|
||||||
|
buf.reserve(std::numeric_limits< long long >::digits10 * 2);
|
||||||
|
|
||||||
|
bool is_signed{ false };
|
||||||
|
|
||||||
|
if (i != last && *i == '-') {
|
||||||
|
buf.push_back(*i++);
|
||||||
|
is_signed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// int
|
||||||
|
if (*i == '0') {
|
||||||
|
buf.push_back(*i++);
|
||||||
|
}
|
||||||
|
else if ('1' <= *i && *i <= '9') {
|
||||||
|
do {
|
||||||
|
buf.push_back(*i++);
|
||||||
|
} while (i != last && '0' <= *i && *i <= '9');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_real{ false };
|
||||||
|
|
||||||
|
// frac
|
||||||
|
if (i != last && *i == '.') {
|
||||||
|
is_real = true;
|
||||||
|
buf.push_back(*i++);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i < '0' || '9' < *i) {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
buf.push_back(*i++);
|
||||||
|
} while (i != last && '0' <= *i && *i <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
// exp
|
||||||
|
if (i != last && (*i == 'e' || *i == 'E')) {
|
||||||
|
is_real = true;
|
||||||
|
buf.push_back(*i++);
|
||||||
|
|
||||||
|
if (i != last && (*i == '-' || *i == '+'))
|
||||||
|
buf.push_back(*i++);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i < '0' || '9' < *i) {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
buf.push_back(*i++);
|
||||||
|
} while (i != last && '0' <= *i && *i <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (is_real) {
|
||||||
|
return variant{ loc, std::stold(buf) };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_signed) {
|
||||||
|
return variant{ loc, std::stoll(buf) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return variant{ loc, std::stoull(buf) };
|
||||||
|
}
|
||||||
|
catch (std::out_of_range const&) {
|
||||||
|
d.error(loc, "number too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_object(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
if (*i != '{')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
|
||||||
|
if (i == last)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (*i == '}') {
|
||||||
|
advance(i, last);
|
||||||
|
return std::map< std::string, variant >{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map< std::string, variant > o;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
auto k = try_parse_string(d, i, last);
|
||||||
|
|
||||||
|
if (d.errors().size() > 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
|
||||||
|
if (i == last || *i != ':')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "unexpected end");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto v = try_parse_any(d, i, last);
|
||||||
|
|
||||||
|
if (d.errors().size() > 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
variant v_v{ std::move(v) };
|
||||||
|
o.emplace(k.get_string(), std::move(v_v));
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
|
||||||
|
if (i == last)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (',' != *i)
|
||||||
|
break;
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
skip_whitespace(i, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == last || *i != '}')
|
||||||
|
return {};
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
return variant{ std::move(loc), std::move(o) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_string(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
|
||||||
|
if (*i != '"') {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i != last && *i != '"') {
|
||||||
|
if (*i == '\\') { // Parse escape sequence.
|
||||||
|
advance(i, last); // skip '\'
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (*i) {
|
||||||
|
case '"':
|
||||||
|
text += '\x22';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
text += '\x5c';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
text += '\x2f';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
text += '\x62';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
text += '\x66';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
text += '\x6e';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
text += '\x72';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
text += '\x74';
|
||||||
|
advance(i, last);
|
||||||
|
break;
|
||||||
|
case 'u': {
|
||||||
|
advance(i, last); // skip u
|
||||||
|
std::uint32_t u[4];
|
||||||
|
|
||||||
|
for (int index = 0; index < 4; ++index, advance(i, last)) {
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t c = (unsigned char)*i;
|
||||||
|
|
||||||
|
if (U'0' <= c && c <= U'9') {
|
||||||
|
u[index] = c - U'0';
|
||||||
|
}
|
||||||
|
else if (U'a' <= c && c <= U'f') {
|
||||||
|
u[index] = (c - U'a') + 10;
|
||||||
|
}
|
||||||
|
else if (U'A' <= c && c <= U'F') {
|
||||||
|
u[index] = (c - U'A') + 10;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d.error(location(), "expected digit");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t utf32 = u[0] << 12 | u[1] << 8 | u[2] << 4 | u[3];
|
||||||
|
|
||||||
|
if (0xdc00 <= utf32 && utf32 <= 0xdfff) {
|
||||||
|
d.error(location(), "invalid unicode code point");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0xd800 <= utf32 && utf32 <= 0xdbff) {
|
||||||
|
// We found a high surrogate, expect a following low-surrogate.
|
||||||
|
auto high = utf32;
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != '\\') {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last); // skip backslash
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != 'u') {
|
||||||
|
d.error(location(), "unexpected character2");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last); // skip 'u'
|
||||||
|
|
||||||
|
for (int index = 0; index < 4; ++index, advance(i, last)) {
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t c = (unsigned char)*i;
|
||||||
|
|
||||||
|
if (U'0' <= c && c <= U'9') {
|
||||||
|
u[index] = c - U'0';
|
||||||
|
}
|
||||||
|
else if (U'a' <= c && c <= U'f') {
|
||||||
|
u[index] = (c - U'a') + 10;
|
||||||
|
}
|
||||||
|
else if (U'A' <= c && c <= U'F') {
|
||||||
|
u[index] = (c - U'A') + 10;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d.error(location(), "expected digit");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t low = u[0] << 12 | u[1] << 8 | u[2] << 4 | u[3];
|
||||||
|
|
||||||
|
utf32 = (high << 10) + low - 0x35fdc00;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream str{ std::ios::out | std::ios::binary };
|
||||||
|
|
||||||
|
code::unicode::Utf8_encoder{}.encode(str, utf32);
|
||||||
|
text += str.str();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_control(i)) {
|
||||||
|
d.error(location(), "invalid character detected");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += *i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(location(), "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i != '"') {
|
||||||
|
d.error(location(), "unexpected character");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(i, last);
|
||||||
|
|
||||||
|
return variant{ std::move(loc), std::move(text) };
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
variant
|
||||||
|
try_parse_any(diagnostics& d, InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
auto loc = location();
|
||||||
|
|
||||||
|
if (i == last) {
|
||||||
|
d.error(loc, "premature end");
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (*i) {
|
||||||
|
case 'n':
|
||||||
|
return try_parse_undefined(d, i, last);
|
||||||
|
case '[':
|
||||||
|
return try_parse_array(d, i, last);
|
||||||
|
case 't':
|
||||||
|
case 'f':
|
||||||
|
return try_parse_boolean(d, i, last);
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
return try_parse_number(d, i, last);
|
||||||
|
case '{':
|
||||||
|
return try_parse_object(d, i, last);
|
||||||
|
case '"':
|
||||||
|
return try_parse_string(d, i, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream str;
|
||||||
|
str << "unexpected character '" << *i << "'";
|
||||||
|
d.error(loc, str.str());
|
||||||
|
|
||||||
|
return variant::undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
void
|
||||||
|
skip_whitespace(InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
while (i != last && is_whitespace< InputIterator >(i))
|
||||||
|
advance(i, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
bool
|
||||||
|
is_whitespace(InputIterator const& c)
|
||||||
|
{
|
||||||
|
switch (*c) {
|
||||||
|
case 0x09: // tab
|
||||||
|
case 0x0a: // lf
|
||||||
|
case 0x0d: // cr
|
||||||
|
case 0x20: // space
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
bool
|
||||||
|
is_control(InputIterator const& c)
|
||||||
|
{
|
||||||
|
switch (*c) {
|
||||||
|
case '\x00':
|
||||||
|
case '\x01':
|
||||||
|
case '\x02':
|
||||||
|
case '\x03':
|
||||||
|
case '\x04':
|
||||||
|
case '\x05':
|
||||||
|
case '\x06':
|
||||||
|
case '\x07':
|
||||||
|
case '\x08':
|
||||||
|
case '\x09':
|
||||||
|
case '\x0A':
|
||||||
|
case '\x0B':
|
||||||
|
case '\x0C':
|
||||||
|
case '\x0D':
|
||||||
|
case '\x0E':
|
||||||
|
case '\x0F':
|
||||||
|
case '\x10':
|
||||||
|
case '\x11':
|
||||||
|
case '\x12':
|
||||||
|
case '\x13':
|
||||||
|
case '\x14':
|
||||||
|
case '\x15':
|
||||||
|
case '\x16':
|
||||||
|
case '\x17':
|
||||||
|
case '\x18':
|
||||||
|
case '\x19':
|
||||||
|
case '\x1A':
|
||||||
|
case '\x1B':
|
||||||
|
case '\x1C':
|
||||||
|
case '\x1D':
|
||||||
|
case '\x1E':
|
||||||
|
case '\x1F':
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics::location
|
||||||
|
location()
|
||||||
|
{
|
||||||
|
return diagnostics::location{ line_, column_ };
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename InputIterator >
|
||||||
|
void
|
||||||
|
advance(InputIterator& i, InputIterator last)
|
||||||
|
{
|
||||||
|
if (i == last)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (*i == '\r') {
|
||||||
|
++line_;
|
||||||
|
column_ = 1;
|
||||||
|
++i;
|
||||||
|
|
||||||
|
if (i != last && *i == '\n')
|
||||||
|
++i;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*i == '\n') {
|
||||||
|
++line_;
|
||||||
|
column_ = 1;
|
||||||
|
++i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++column_;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t line_{ 1 };
|
||||||
|
std::uint32_t column_{ 1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
163
code/json/pointer.cxx
Normal file
163
code/json/pointer.cxx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#include <code/json/pointer.hxx>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
pointer::
|
||||||
|
pointer(std::string const& expression)
|
||||||
|
: refs_{parse_refs(expression)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
optional< variant >
|
||||||
|
pointer::
|
||||||
|
read(variant const& source) const
|
||||||
|
{
|
||||||
|
auto const* current = &source;
|
||||||
|
auto refs = refs_;
|
||||||
|
|
||||||
|
while (refs.size() > 0) {
|
||||||
|
auto cref = refs.front();
|
||||||
|
refs.pop();
|
||||||
|
|
||||||
|
if (current->is_object()) {
|
||||||
|
if (!current->contains(cref))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
current = ¤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 code::json
|
41
code/json/pointer.hxx
Normal file
41
code/json/pointer.hxx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#ifndef code__json__pointer_hxx_
|
||||||
|
#define code__json__pointer_hxx_
|
||||||
|
|
||||||
|
#include <code/json/exception.hxx>
|
||||||
|
#include <code/json/optional.hxx>
|
||||||
|
#include <code/json/read.hxx>
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
class pointer {
|
||||||
|
public:
|
||||||
|
explicit
|
||||||
|
pointer(std::string const& expression);
|
||||||
|
|
||||||
|
optional< variant >
|
||||||
|
read(variant const& source) const;
|
||||||
|
|
||||||
|
void
|
||||||
|
write(variant& target, variant value) const;
|
||||||
|
|
||||||
|
friend
|
||||||
|
std::string
|
||||||
|
to_string(pointer const& ptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::queue< std::string >
|
||||||
|
parse_refs(std::string const& expression);
|
||||||
|
|
||||||
|
std::queue< std::string > refs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string
|
||||||
|
to_string(pointer const& ptr);
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
95
code/json/pointer.test.cxx
Normal file
95
code/json/pointer.test.cxx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
#include <code/json/pointer.hxx>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||||
|
#define TEST_TRUE(x) if (!(x)) return __LINE__;
|
||||||
|
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
auto resolve = [](std::string const& data, char const* path)
|
||||||
|
{
|
||||||
|
code::json::pointer ptr{std::string{path}};
|
||||||
|
return ptr.read(code::json::read(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_TEST("simple object")
|
||||||
|
{
|
||||||
|
std::string json = R"JSON(
|
||||||
|
{ "haystack": "needle" }
|
||||||
|
)JSON";
|
||||||
|
|
||||||
|
auto variant = resolve(json, "/haystack");
|
||||||
|
|
||||||
|
TEST_TRUE(variant->is_string());
|
||||||
|
TEST_EQUAL(variant->get_string(), "needle");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("simple array")
|
||||||
|
{
|
||||||
|
std::string json = R"JSON(
|
||||||
|
[ "needle" ]
|
||||||
|
)JSON";
|
||||||
|
|
||||||
|
auto variant = resolve(json, "/0");
|
||||||
|
|
||||||
|
TEST_TRUE(variant->is_string());
|
||||||
|
TEST_EQUAL(variant->get_string(), "needle");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("complex")
|
||||||
|
{
|
||||||
|
std::string json = R"JSON(
|
||||||
|
{
|
||||||
|
"employees": [
|
||||||
|
{
|
||||||
|
"name": "Doe, John",
|
||||||
|
"departments": ["HQ"],
|
||||||
|
"age": 37
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Doe, Jane",
|
||||||
|
"departments": ["Software", "Finance"],
|
||||||
|
"age": 29
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)JSON";
|
||||||
|
|
||||||
|
auto emp_0_name = resolve(json, "/employees/0/name");
|
||||||
|
auto emp_0_department_0 = resolve(json, "/employees/0/departments/0");
|
||||||
|
auto emp_0_age = resolve(json, "/employees/0/age");
|
||||||
|
|
||||||
|
auto emp_1_name = resolve(json, "/employees/1/name");
|
||||||
|
auto emp_1_department_0 = resolve(json, "/employees/1/departments/0");
|
||||||
|
auto emp_1_department_1 = resolve(json, "/employees/1/departments/1");
|
||||||
|
auto emp_1_age = resolve(json, "/employees/1/age");
|
||||||
|
|
||||||
|
TEST_TRUE(emp_0_name->is_string());
|
||||||
|
TEST_TRUE(emp_0_department_0->is_string());
|
||||||
|
TEST_TRUE(emp_0_age->is_number());
|
||||||
|
TEST_TRUE(emp_0_age->is_unsigned());
|
||||||
|
|
||||||
|
TEST_TRUE(emp_1_name->is_string());
|
||||||
|
TEST_TRUE(emp_1_department_0->is_string());
|
||||||
|
TEST_TRUE(emp_1_department_1->is_string());
|
||||||
|
TEST_TRUE(emp_1_age->is_number());
|
||||||
|
TEST_TRUE(emp_1_age->is_unsigned());
|
||||||
|
|
||||||
|
TEST_EQUAL(emp_0_name->get_string(), "Doe, John");
|
||||||
|
TEST_EQUAL(emp_0_department_0->get_string(), "HQ");
|
||||||
|
TEST_EQUAL(emp_0_age->get_number< signed int >(), 37);
|
||||||
|
TEST_EQUAL(emp_0_age->get_number< unsigned int >(), 37);
|
||||||
|
|
||||||
|
TEST_EQUAL(emp_1_name->get_string(), "Doe, Jane");
|
||||||
|
TEST_EQUAL(emp_1_department_0->get_string(), "Software");
|
||||||
|
TEST_EQUAL(emp_1_department_1->get_string(), "Finance");
|
||||||
|
TEST_EQUAL(emp_1_age->get_number< signed int >(), 29);
|
||||||
|
TEST_EQUAL(emp_1_age->get_number< unsigned int >(), 29);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
74
code/json/read.hxx
Normal file
74
code/json/read.hxx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#ifndef code__json__read_hxx_
|
||||||
|
#define code__json__read_hxx_
|
||||||
|
|
||||||
|
#include <code/json/optional.hxx>
|
||||||
|
#include <code/json/parser.hxx>
|
||||||
|
#include <code/json/variant.hxx>
|
||||||
|
|
||||||
|
#include <istream>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
inline variant
|
||||||
|
read(std::string const& input)
|
||||||
|
{
|
||||||
|
diagnostics d;
|
||||||
|
|
||||||
|
auto first = input.begin();
|
||||||
|
return parser{}.parse(d, first, input.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant
|
||||||
|
read(diagnostics& d, std::string const& input)
|
||||||
|
{
|
||||||
|
auto first = input.begin();
|
||||||
|
return parser{}.parse(d, first, input.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline optional< variant >
|
||||||
|
try_read(diagnostics& d, std::string const& input)
|
||||||
|
{
|
||||||
|
auto first = input.begin();
|
||||||
|
return parser{}.try_parse(d, first, input.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline optional< variant >
|
||||||
|
try_read(std::string const& input)
|
||||||
|
{
|
||||||
|
diagnostics d;
|
||||||
|
|
||||||
|
auto first = input.begin();
|
||||||
|
return parser{}.try_parse(d, first, input.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant
|
||||||
|
read(diagnostics& d, std::istream& input)
|
||||||
|
{
|
||||||
|
input.unsetf(std::istream::skipws);
|
||||||
|
|
||||||
|
std::istreambuf_iterator< char > first{ input };
|
||||||
|
return parser{}.parse(d, first, std::istreambuf_iterator< char >{});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant
|
||||||
|
read(std::istream& input)
|
||||||
|
{
|
||||||
|
diagnostics d;
|
||||||
|
return read(d, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline optional< variant >
|
||||||
|
try_read(std::istream& input)
|
||||||
|
{
|
||||||
|
input.unsetf(std::istream::skipws);
|
||||||
|
|
||||||
|
diagnostics d;
|
||||||
|
|
||||||
|
std::istreambuf_iterator< char > first{ input };
|
||||||
|
return parser{}.try_parse(d, first, std::istreambuf_iterator< char >{});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
607
code/json/read.test.cxx
Normal file
607
code/json/read.test.cxx
Normal file
@ -0,0 +1,607 @@
|
|||||||
|
#include <code/json/parser.hxx>
|
||||||
|
#include <code/json/read.hxx>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||||
|
#define TEST_TRUE(x) if (!(x)) return __LINE__;
|
||||||
|
#define TEST_FALSE(x) if ((x)) return __LINE__;
|
||||||
|
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("boolean: parse false")
|
||||||
|
{
|
||||||
|
std::string const text{ "false" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_boolean());
|
||||||
|
TEST_EQUAL(var.get_boolean(), false);
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: parse false, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " false " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_boolean());
|
||||||
|
TEST_EQUAL(var.get_boolean(), false);
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: parse true")
|
||||||
|
{
|
||||||
|
std::string const text{ "true" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_boolean());
|
||||||
|
TEST_EQUAL(var.get_boolean(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("boolean: parse true, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " true " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_boolean());
|
||||||
|
TEST_EQUAL(var.get_boolean(), true);
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("null: parse null")
|
||||||
|
{
|
||||||
|
std::string const text{ "null" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_undefined());
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("null: parse null, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " null " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_undefined());
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse empty")
|
||||||
|
{
|
||||||
|
std::string const text{ "[]" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_array());
|
||||||
|
TEST_TRUE(var.empty());
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse empty, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " [ ] " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_array());
|
||||||
|
TEST_TRUE(var.empty());
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse null element")
|
||||||
|
{
|
||||||
|
std::string const text{ "[null]" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_array());
|
||||||
|
TEST_EQUAL(var.location().line, 1);
|
||||||
|
TEST_EQUAL(var.location().column, 1);
|
||||||
|
TEST_FALSE(var.empty());
|
||||||
|
TEST_EQUAL(var.size(), 1);
|
||||||
|
TEST_TRUE(var.get(0).is_undefined());
|
||||||
|
TEST_EQUAL(var.get(0).location().line, 1);
|
||||||
|
TEST_EQUAL(var.get(0).location().column, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse null element, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " [ null ] " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_array());
|
||||||
|
TEST_FALSE(var.empty());
|
||||||
|
TEST_EQUAL(var.size(), 1);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get(0).is_undefined());
|
||||||
|
TEST_EQUAL(var.get(0).location().line, 1);
|
||||||
|
TEST_EQUAL(var.get(0).location().column, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse double null elements")
|
||||||
|
{
|
||||||
|
std::string const text{ "[null,null]" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_array());
|
||||||
|
TEST_FALSE(var.empty());
|
||||||
|
TEST_EQUAL(var.size(), 2);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get(0).is_undefined());
|
||||||
|
TEST_EQUAL(var.get(0).location().line, 1);
|
||||||
|
TEST_EQUAL(var.get(0).location().column, 2);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get(1).is_undefined());
|
||||||
|
TEST_EQUAL(var.get(1).location().line, 1);
|
||||||
|
TEST_EQUAL(var.get(1).location().column, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse double null elements, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " [ null , null ] " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE( var.is_array());
|
||||||
|
TEST_FALSE(var.empty());
|
||||||
|
TEST_EQUAL(var.size(), 2);
|
||||||
|
|
||||||
|
TEST_TRUE( var.get(0).is_undefined());
|
||||||
|
TEST_EQUAL(var.get(0).location().line, 1);
|
||||||
|
TEST_EQUAL(var.get(0).location().column, 4);
|
||||||
|
|
||||||
|
TEST_TRUE( var.get(1).is_undefined());
|
||||||
|
TEST_EQUAL(var.get(1).location().line, 1);
|
||||||
|
TEST_EQUAL(var.get(1).location().column, 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("array: parse nested")
|
||||||
|
{
|
||||||
|
std::string const text{ " [ [ null , null ] , [ null ] ]" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_array());
|
||||||
|
TEST_FALSE(var.empty());
|
||||||
|
TEST_EQUAL(var.size(), 2);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get(0).is_array());
|
||||||
|
TEST_FALSE(var.get(0).empty());
|
||||||
|
TEST_EQUAL(var.get(0).size(), 2);
|
||||||
|
TEST_TRUE(var.get(0).get(0).is_undefined());
|
||||||
|
TEST_TRUE(var.get(0).get(1).is_undefined());
|
||||||
|
|
||||||
|
TEST_TRUE(var. get(1).is_array());
|
||||||
|
TEST_FALSE(var.get(1).empty());
|
||||||
|
TEST_EQUAL(var.get(1).size(), 1);
|
||||||
|
TEST_TRUE(var. get(1).get(0).is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse empty")
|
||||||
|
{
|
||||||
|
std::string const text{ "{}" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_TRUE(var.keys().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse empty, with whitespace")
|
||||||
|
{
|
||||||
|
std::string const text{ " { } " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_EQUAL(var.keys().size(), 0UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse 1-element object")
|
||||||
|
{
|
||||||
|
std::string const text{ "{\"key\":null}" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_EQUAL(var.keys().size(), 1UL);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get("key").is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse 1-element object (with whitespace)")
|
||||||
|
{
|
||||||
|
std::string const text{ " { \"key\" : null } " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_EQUAL(var.keys().size(), 1UL);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get("key").is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse 2-element object")
|
||||||
|
{
|
||||||
|
std::string const text{ "{\"key1\":null,\"key2\":null}" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_EQUAL(var.keys().size(), 2UL);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get("key1").is_undefined());
|
||||||
|
TEST_TRUE(var.get("key2").is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse 2-element object (with whitespace)")
|
||||||
|
{
|
||||||
|
std::string const text{ " { \"key1\" : null, \"key2\" : null } " };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_EQUAL(var.keys().size(), 2UL);
|
||||||
|
|
||||||
|
TEST_TRUE(var.get("key1").is_undefined());
|
||||||
|
TEST_TRUE(var.get("key2").is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("object: parse nested object")
|
||||||
|
{
|
||||||
|
std::string const text{ R"#({
|
||||||
|
"k1": {
|
||||||
|
"k1": null
|
||||||
|
},
|
||||||
|
"k2": {
|
||||||
|
"k1": null,
|
||||||
|
"k2": null
|
||||||
|
}
|
||||||
|
})#" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_object());
|
||||||
|
TEST_EQUAL(var.keys().size(), 2UL);
|
||||||
|
|
||||||
|
// Nested object 1.
|
||||||
|
auto object1 = var.get("k1");
|
||||||
|
TEST_TRUE(object1.is_object());
|
||||||
|
TEST_EQUAL(object1.keys().size(), 1UL);
|
||||||
|
TEST_TRUE(object1.get("k1").is_undefined());
|
||||||
|
|
||||||
|
// Nested object 2.
|
||||||
|
auto object2 = var.get("k2");
|
||||||
|
TEST_TRUE(object2.is_object());
|
||||||
|
TEST_EQUAL(object2.keys().size(), 2UL);
|
||||||
|
TEST_TRUE(object2.get("k1").is_undefined());
|
||||||
|
TEST_TRUE(object2.get("k2").is_undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: integer: 0")
|
||||||
|
{
|
||||||
|
std::string const text{ "0" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_FALSE(var.is_signed());
|
||||||
|
TEST_TRUE(var.is_unsigned());
|
||||||
|
TEST_EQUAL(var.get_number< unsigned long long >(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: integer: 1")
|
||||||
|
{
|
||||||
|
std::string const text{ "1" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_FALSE(var.is_signed());
|
||||||
|
TEST_TRUE(var.is_unsigned());
|
||||||
|
TEST_EQUAL(var.get_number< unsigned long long >(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: integer: -1")
|
||||||
|
{
|
||||||
|
std::string const text{ "-1" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_signed());
|
||||||
|
TEST_FALSE(var.is_unsigned());
|
||||||
|
TEST_EQUAL(var.get_number< long long >(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: integer: 18446744073709551615")
|
||||||
|
{
|
||||||
|
std::string const text{ "18446744073709551615" };
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_unsigned());
|
||||||
|
TEST_FALSE(var.is_signed());
|
||||||
|
TEST_EQUAL(var.get_number< unsigned long long >(), 18446744073709551615ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 0.0")
|
||||||
|
{
|
||||||
|
std::string const text{ "0.0" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1.0")
|
||||||
|
{
|
||||||
|
std::string const text{ "1.0" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1e3")
|
||||||
|
{
|
||||||
|
std::string const text{ "1e3" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1E3")
|
||||||
|
{
|
||||||
|
std::string const text{ "1E3" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1e+3")
|
||||||
|
{
|
||||||
|
std::string const text{ "1e+3" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1E+3")
|
||||||
|
{
|
||||||
|
std::string const text{ "1E+3" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1e-3")
|
||||||
|
{
|
||||||
|
std::string const text{ "1e-3" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("number: real: 1E-3")
|
||||||
|
{
|
||||||
|
std::string const text{ "1E-3" };
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_real());
|
||||||
|
TEST_EQUAL(var.get_number< double >(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("string: empty string")
|
||||||
|
{
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(R"("")");
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_string());
|
||||||
|
TEST_EQUAL(var.get_string(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("string: escape sequences")
|
||||||
|
{
|
||||||
|
std::pair< std::string, std::string > escapes[] = {
|
||||||
|
{ R"("\"")", "\x22" }, // " (quotation mark)
|
||||||
|
{ R"("\\")", "\x5C" }, // \ (reverse solidus)
|
||||||
|
{ R"("\/")", "\x2F" }, // / (solidus)
|
||||||
|
{ R"("\b")", "\x62" }, // b (backspace)
|
||||||
|
{ R"("\f")", "\x66" }, // f (form feed)
|
||||||
|
{ R"("\n")", "\x6E" }, // n (line feed)
|
||||||
|
{ R"("\r")", "\x72" }, // r (carriage return)
|
||||||
|
{ R"("\t")", "\x74" } // t (tab)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& esc : escapes) {
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(esc.first);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_string());
|
||||||
|
TEST_EQUAL(var.get_string(), esc.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_TEST("string: utf-16 escape sequences")
|
||||||
|
{
|
||||||
|
std::pair< std::string, std::string > escapes[]{
|
||||||
|
{ R"("{\ud834\udd1e}")", "{\xf0\x9d\x84\x9e}" }, // MUSICAL SYMBOL G CLEF
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& escape : escapes) {
|
||||||
|
std::string text = escape.first;
|
||||||
|
|
||||||
|
code::json::diagnostics diag;
|
||||||
|
auto var = code::json::read(diag, text);
|
||||||
|
|
||||||
|
TEST_EQUAL(diag.errors().size(), 0);
|
||||||
|
TEST_EQUAL(diag.warnings().size(), 0);
|
||||||
|
|
||||||
|
TEST_TRUE(var.is_string());
|
||||||
|
TEST_EQUAL(var.get_string(), escape.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
23
code/json/resolve.test.cxx
Normal file
23
code/json/resolve.test.cxx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include <code/json/pointer.hxx>
|
||||||
|
#include <code/json/write.hxx>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
std::stringstream str;
|
||||||
|
str << std::cin.rdbuf();
|
||||||
|
|
||||||
|
auto var = code::json::read(str.str());
|
||||||
|
|
||||||
|
std::cout << code::json::write(var);
|
||||||
|
|
||||||
|
code::json::resolve(str.str(), argv[1]);
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
285
code/json/variant.hxx
Normal file
285
code/json/variant.hxx
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
#ifndef code__json__variant_hxx_
|
||||||
|
#define code__json__variant_hxx_
|
||||||
|
|
||||||
|
#include <code/json/diagnostics.hxx>
|
||||||
|
#include <code/json/exception.hxx>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class emitter;
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
//! Variant holding any valid JSON value.
|
||||||
|
class variant {
|
||||||
|
public:
|
||||||
|
//! Array iterator type.
|
||||||
|
using iterator = std::vector< variant >::iterator;
|
||||||
|
|
||||||
|
//! Array iterator type.
|
||||||
|
using const_iterator = std::vector< variant >::const_iterator;
|
||||||
|
|
||||||
|
//! Type used to denote an undefined variant.
|
||||||
|
struct undefined_t { };
|
||||||
|
static constexpr undefined_t undefined{};
|
||||||
|
|
||||||
|
//! Construct an undefined variant.
|
||||||
|
variant();
|
||||||
|
|
||||||
|
//! Construct an undefined variant.
|
||||||
|
variant(undefined_t);
|
||||||
|
|
||||||
|
//! Construct a variant holding a boolean value.
|
||||||
|
variant(bool value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a signed number.
|
||||||
|
variant(short int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a signed number.
|
||||||
|
variant(int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a signed number.
|
||||||
|
variant(long int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a signed number.
|
||||||
|
variant(long long int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding an unsigned number.
|
||||||
|
variant(unsigned short int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding an unsigned number.
|
||||||
|
variant(unsigned int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding an unsigned number.
|
||||||
|
variant(unsigned long int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding an unsigned number.
|
||||||
|
variant(unsigned long long int value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a real number.
|
||||||
|
variant(float value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a real number.
|
||||||
|
variant(double value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a real number.
|
||||||
|
variant(long double value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a string.
|
||||||
|
variant(std::string value);
|
||||||
|
|
||||||
|
//! Construct a variant holding a string.
|
||||||
|
variant(char const* value);
|
||||||
|
|
||||||
|
//! Construct a variant holding an array.
|
||||||
|
variant(std::vector< variant > value);
|
||||||
|
|
||||||
|
//! Construct a variant holding an object.
|
||||||
|
variant(std::map< std::string, variant > value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, undefined_t);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, bool value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, short int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, long int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, long long int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, unsigned short int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, unsigned int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, unsigned long int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, unsigned long long int value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, float value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, double value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, long double value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, std::string value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, char const* value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, std::vector< variant > value);
|
||||||
|
|
||||||
|
variant(diagnostics::location location, std::map< std::string, variant > value);
|
||||||
|
|
||||||
|
//! Check if this variant is undefined.
|
||||||
|
bool
|
||||||
|
is_undefined() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds a boolean.
|
||||||
|
bool
|
||||||
|
is_boolean() const;
|
||||||
|
|
||||||
|
//! Get boolean value.
|
||||||
|
bool
|
||||||
|
get_boolean() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds a number value.
|
||||||
|
bool
|
||||||
|
is_number() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds a signed value.
|
||||||
|
bool
|
||||||
|
is_signed() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds an unsigned value.
|
||||||
|
bool
|
||||||
|
is_unsigned() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds a real value.
|
||||||
|
bool
|
||||||
|
is_real() const;
|
||||||
|
|
||||||
|
//! Get number value.
|
||||||
|
template< typename ArithmeticType >
|
||||||
|
ArithmeticType
|
||||||
|
get_number() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds a string.
|
||||||
|
bool
|
||||||
|
is_string() const;
|
||||||
|
|
||||||
|
//! Get string value.
|
||||||
|
std::string const&
|
||||||
|
get_string() const;
|
||||||
|
|
||||||
|
//! Check if this variant holds an array.
|
||||||
|
bool
|
||||||
|
is_array() const;
|
||||||
|
|
||||||
|
//! Check if empty.
|
||||||
|
bool
|
||||||
|
empty() const;
|
||||||
|
|
||||||
|
//! Get array size.
|
||||||
|
std::size_t
|
||||||
|
size() const;
|
||||||
|
|
||||||
|
//! Get array iterator.
|
||||||
|
iterator
|
||||||
|
begin();
|
||||||
|
|
||||||
|
//! Get array iterator.
|
||||||
|
const_iterator
|
||||||
|
begin() const;
|
||||||
|
|
||||||
|
//! Get array iterator.
|
||||||
|
const_iterator
|
||||||
|
cbegin() const;
|
||||||
|
|
||||||
|
//! Get array iterator.
|
||||||
|
iterator
|
||||||
|
end();
|
||||||
|
|
||||||
|
//! Get array iterator.
|
||||||
|
const_iterator
|
||||||
|
end() const;
|
||||||
|
|
||||||
|
//! Get array iterator.
|
||||||
|
const_iterator
|
||||||
|
cend() const;
|
||||||
|
|
||||||
|
//! Get value at array index.
|
||||||
|
variant&
|
||||||
|
get(std::size_t index);
|
||||||
|
|
||||||
|
//! Get value at array index.
|
||||||
|
variant const&
|
||||||
|
get(std::size_t index) const;
|
||||||
|
|
||||||
|
//! Get value at array index.
|
||||||
|
variant const&
|
||||||
|
cget(std::size_t index) const;
|
||||||
|
|
||||||
|
//! Push value to back of array.
|
||||||
|
void
|
||||||
|
push_back(variant v);
|
||||||
|
|
||||||
|
//! Remove element from array.
|
||||||
|
void
|
||||||
|
erase(std::size_t index);
|
||||||
|
|
||||||
|
//! Check if this variant holds an object.
|
||||||
|
bool
|
||||||
|
is_object() const;
|
||||||
|
|
||||||
|
//! Check if object contains key.
|
||||||
|
bool
|
||||||
|
contains(std::string const& key) const;
|
||||||
|
|
||||||
|
//! Get object keys.
|
||||||
|
std::set< std::string >
|
||||||
|
keys() const;
|
||||||
|
|
||||||
|
//! Get object value.
|
||||||
|
variant&
|
||||||
|
get(std::string const& key);
|
||||||
|
|
||||||
|
//! Get object value.
|
||||||
|
variant const&
|
||||||
|
get(std::string const& key) const;
|
||||||
|
|
||||||
|
//! Get object value.
|
||||||
|
variant const&
|
||||||
|
cget(std::string const& key) const;
|
||||||
|
|
||||||
|
//! Set object value.
|
||||||
|
void
|
||||||
|
set(std::string const& key, variant v);
|
||||||
|
|
||||||
|
//! Remove element from object.
|
||||||
|
void
|
||||||
|
erase(std::string const& key);
|
||||||
|
|
||||||
|
//! Get the location of this value.
|
||||||
|
diagnostics::location const&
|
||||||
|
location() const;
|
||||||
|
|
||||||
|
template< typename Visitor >
|
||||||
|
friend void
|
||||||
|
visit(Visitor&& visitor, variant const& value)
|
||||||
|
{
|
||||||
|
std::visit(std::forward< Visitor >(visitor), value.value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class emitter;
|
||||||
|
|
||||||
|
//! Internal variant type.
|
||||||
|
using variant_type = std::variant< undefined_t,
|
||||||
|
bool,
|
||||||
|
long long int,
|
||||||
|
unsigned long long int,
|
||||||
|
long double,
|
||||||
|
std::string,
|
||||||
|
std::vector< variant >,
|
||||||
|
std::map< std::string, variant > >;
|
||||||
|
|
||||||
|
//! Optional source location of this variant.
|
||||||
|
diagnostics::location location_;
|
||||||
|
|
||||||
|
//! The value.
|
||||||
|
variant_type value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#include <code/json/variant.ixx>
|
||||||
|
#include <code/json/variant.txx>
|
||||||
|
|
||||||
|
#endif
|
409
code/json/variant.ixx
Normal file
409
code/json/variant.ixx
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
inline variant::variant() : value_{ undefined_t{} }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(undefined_t) : value_{ undefined_t{} }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(bool value) : value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(short int value)
|
||||||
|
: value_{ static_cast< long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(int value)
|
||||||
|
: value_{ static_cast< long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(long int value)
|
||||||
|
: value_{ static_cast< long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(long long int value) : value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(unsigned short int value)
|
||||||
|
: value_{ static_cast< unsigned long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(unsigned int value)
|
||||||
|
: value_{ static_cast< unsigned long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(unsigned long int value)
|
||||||
|
: value_{ static_cast< unsigned long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(unsigned long long int value) : value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(float value)
|
||||||
|
: value_{ static_cast< long double >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(double value)
|
||||||
|
: value_{ static_cast< long double >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(long double value) : value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(std::string value) : value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(char const* value) : value_{ std::string{ value } }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(std::vector< variant > value) : value_{ std::move(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(std::map< std::string, variant > value) : value_{ std::move(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, undefined_t)
|
||||||
|
: location_{ std::move(location) }, value_{ undefined_t{} }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, bool value)
|
||||||
|
: location_{ std::move(location) }, value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, short int value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, int value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, long int value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, long long int value)
|
||||||
|
: location_{ std::move(location) }, value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location,
|
||||||
|
unsigned short int value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< unsigned long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, unsigned int value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< unsigned long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, unsigned long int value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< unsigned long long int >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location,
|
||||||
|
unsigned long long int value)
|
||||||
|
: location_{ std::move(location) }, value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, float value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< long double >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, double value)
|
||||||
|
: location_{ std::move(location) },
|
||||||
|
value_{ static_cast< long double >(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, long double value)
|
||||||
|
: location_{ std::move(location) }, value_{ value }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, std::string value)
|
||||||
|
: location_{ std::move(location) }, value_{ std::move(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, char const* value)
|
||||||
|
: location_{ std::move(location) }, value_{ std::string{ value } }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, std::vector< variant > value)
|
||||||
|
: location_{ std::move(location) }, value_{ std::move(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline variant::variant(diagnostics::location location, std::map< std::string, variant > value)
|
||||||
|
: location_{ std::move(location) }, value_{ std::move(value) }
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_undefined() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< undefined_t >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_boolean() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< bool >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::get_boolean() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< bool >(value_))
|
||||||
|
return std::get< bool >(value_);
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_number() const
|
||||||
|
{
|
||||||
|
return is_signed() || is_unsigned() || is_real();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_signed() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< long long int >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_unsigned() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< unsigned long long int >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_real() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< long double >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_string() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< std::string >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string const&
|
||||||
|
variant::get_string() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::string >(value_))
|
||||||
|
return std::get< std::string >(value_);
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_array() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< std::vector< variant > >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::empty() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).empty();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::size_t
|
||||||
|
variant::size() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).size();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant::iterator
|
||||||
|
variant::begin()
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).begin();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant::const_iterator
|
||||||
|
variant::begin() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).begin();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant::const_iterator
|
||||||
|
variant::cbegin() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).cbegin();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant::iterator
|
||||||
|
variant::end()
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).end();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant::const_iterator
|
||||||
|
variant::end() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).end();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant::const_iterator
|
||||||
|
variant::cend() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_).cend();
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant&
|
||||||
|
variant::get(std::size_t index)
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_)[index];
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant const&
|
||||||
|
variant::get(std::size_t index) const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
return std::get< std::vector< variant > >(value_)[index];
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant const&
|
||||||
|
variant::cget(std::size_t index) const
|
||||||
|
{
|
||||||
|
return get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
variant::push_back(variant v)
|
||||||
|
{
|
||||||
|
if (!std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
throw invalid_type{};
|
||||||
|
|
||||||
|
std::get< std::vector< variant > >(value_).push_back(std::move(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
variant::erase(std::size_t index)
|
||||||
|
{
|
||||||
|
if (!std::holds_alternative< std::vector< variant > >(value_))
|
||||||
|
throw invalid_type{};
|
||||||
|
|
||||||
|
auto& v = std::get< std::vector< variant > >(value_);
|
||||||
|
|
||||||
|
if (index >= v.size())
|
||||||
|
throw std::out_of_range{"index"};
|
||||||
|
|
||||||
|
v.erase(v.begin() + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::is_object() const
|
||||||
|
{
|
||||||
|
return std::holds_alternative< std::map< std::string, variant > >(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
variant::contains(std::string const& key) const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::map< std::string, variant > >(value_))
|
||||||
|
return std::get< std::map< std::string, variant > >(value_).count(key) > 0;
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::set< std::string >
|
||||||
|
variant::keys() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::map< std::string, variant > >(value_)) {
|
||||||
|
std::set< std::string > keys;
|
||||||
|
|
||||||
|
for (auto const& j : std::get< std::map< std::string, variant > >(value_))
|
||||||
|
keys.emplace(j.first);
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant&
|
||||||
|
variant::get(std::string const& key)
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::map< std::string, variant > >(value_))
|
||||||
|
return std::get< std::map< std::string, variant > >(value_).at(key);
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant const&
|
||||||
|
variant::get(std::string const& key) const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::map< std::string, variant > >(value_))
|
||||||
|
return std::get< std::map< std::string, variant > >(value_).at(key);
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline variant const&
|
||||||
|
variant::cget(std::string const& key) const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative< std::map< std::string, variant > >(value_))
|
||||||
|
return std::get< std::map< std::string, variant > >(value_).at(key);
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
variant::set(std::string const& key, variant v)
|
||||||
|
{
|
||||||
|
if (!std::holds_alternative< std::map< std::string, variant > >(value_))
|
||||||
|
throw invalid_type{};
|
||||||
|
|
||||||
|
std::get< std::map< std::string, variant > >(value_)[key] = std::move(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
variant::erase(std::string const& key)
|
||||||
|
{
|
||||||
|
if (!std::holds_alternative< std::map< std::string, variant > >(value_))
|
||||||
|
throw invalid_type{};
|
||||||
|
|
||||||
|
std::get< std::map< std::string, variant > >(value_).erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline diagnostics::location const&
|
||||||
|
variant::location() const
|
||||||
|
{
|
||||||
|
return location_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json
|
19
code/json/variant.txx
Normal file
19
code/json/variant.txx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
template< typename ArithmeticType >
|
||||||
|
ArithmeticType
|
||||||
|
variant::get_number() const
|
||||||
|
{
|
||||||
|
if constexpr (std::is_arithmetic_v< ArithmeticType >) {
|
||||||
|
if (std::holds_alternative< long long int >(value_))
|
||||||
|
return static_cast< ArithmeticType >(std::get< long long int >(value_));
|
||||||
|
else if (std::holds_alternative< unsigned long long int >(value_))
|
||||||
|
return static_cast< ArithmeticType >(std::get< unsigned long long int >(value_));
|
||||||
|
else if (std::holds_alternative< long double >(value_))
|
||||||
|
return static_cast< ArithmeticType >(std::get< long double >(value_));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw invalid_type{};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json
|
37
code/json/version.hxx.in
Normal file
37
code/json/version.hxx.in
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#ifndef code__json__version_hxx_
|
||||||
|
#define code__json__version_hxx_
|
||||||
|
|
||||||
|
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
|
||||||
|
//
|
||||||
|
// AAAAA - major version number
|
||||||
|
// BBBBB - minor version number
|
||||||
|
// CCCCC - bugfix version number
|
||||||
|
// DDD - alpha / beta (DDD + 500) version number
|
||||||
|
// E - final (0) / snapshot (1)
|
||||||
|
//
|
||||||
|
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
|
||||||
|
//
|
||||||
|
// Version AAAAABBBBBCCCCCDDDE
|
||||||
|
//
|
||||||
|
// 0.1.0 0000000001000000000
|
||||||
|
// 0.1.2 0000000001000020000
|
||||||
|
// 1.2.3 0000100002000030000
|
||||||
|
// 2.2.0-a.1 0000200001999990010
|
||||||
|
// 3.0.0-b.2 0000299999999995020
|
||||||
|
// 2.2.0-a.1.z 0000200001999990011
|
||||||
|
//
|
||||||
|
#define LIBCODE_JSON_VERSION $libcode_json.version.project_number$ULL
|
||||||
|
#define LIBCODE_JSON_VERSION_STR "$libcode_json.version.project$"
|
||||||
|
#define LIBCODE_JSON_VERSION_ID "$libcode_json.version.project_id$"
|
||||||
|
#define LIBCODE_JSON_VERSION_FULL "$libcode_json.version$"
|
||||||
|
|
||||||
|
#define LIBCODE_JSON_VERSION_MAJOR $libcode_json.version.major$
|
||||||
|
#define LIBCODE_JSON_VERSION_MINOR $libcode_json.version.minor$
|
||||||
|
#define LIBCODE_JSON_VERSION_PATCH $libcode_json.version.patch$
|
||||||
|
|
||||||
|
#define LIBCODE_JSON_PRE_RELEASE $libcode_json.version.pre_release$
|
||||||
|
|
||||||
|
#define LIBCODE_JSON_SNAPSHOT_SN $libcode_json.version.snapshot_sn$ULL
|
||||||
|
#define LIBCODE_JSON_SNAPSHOT_ID "$libcode_json.version.snapshot_id$"
|
||||||
|
|
||||||
|
#endif
|
31
code/json/write.hxx
Normal file
31
code/json/write.hxx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef code__json__write_hxx_
|
||||||
|
#define code__json__write_hxx_
|
||||||
|
|
||||||
|
#include <code/json/emitter.hxx>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace code::json {
|
||||||
|
|
||||||
|
class variant;
|
||||||
|
|
||||||
|
inline void
|
||||||
|
write(std::ostream& os, variant const& v)
|
||||||
|
{
|
||||||
|
visit(emitter{ os }, v);
|
||||||
|
os << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string
|
||||||
|
write(variant const& json)
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
write(str, json);
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace code::json
|
||||||
|
|
||||||
|
#endif
|
31
code/json/write.test.cxx
Normal file
31
code/json/write.test.cxx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#include <code/json/variant.hxx>
|
||||||
|
#include <code/json/write.hxx>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#define DEFINE_TEST(x) std::cout << x << '\n';
|
||||||
|
#define TEST_EQUAL(x, y) if ((x) != (y)) return __LINE__;
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
DEFINE_TEST("write")
|
||||||
|
{
|
||||||
|
auto document = std::map< std::string, code::json::variant >{
|
||||||
|
{ { "title", "Coca-Cola Regular 1.5L" },
|
||||||
|
{ "gtin13", "5449000139306" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
std::stringstream output;
|
||||||
|
code::json::write(output, document);
|
||||||
|
|
||||||
|
TEST_EQUAL(output.str(), R"({
|
||||||
|
"gtin13": "5449000139306",
|
||||||
|
"title": "Coca-Cola Regular 1.5L"
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
13
manifest
Normal file
13
manifest
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
: 1
|
||||||
|
name: libcode-json
|
||||||
|
version: 0.1.0-a.0.z
|
||||||
|
language: c++
|
||||||
|
summary: libcode-json C++ library
|
||||||
|
license: BSD-4-Clause
|
||||||
|
description-file: README.md
|
||||||
|
url: https://helloryan.se/code/
|
||||||
|
email: ryan@helloryan.se
|
||||||
|
depends: * build2 >= 0.17.0
|
||||||
|
depends: * bpkg >= 0.17.0
|
||||||
|
depends: libcode-validation ^0.1.0-
|
||||||
|
depends: libcode-unicode ^0.1.0-
|
10
repositories.manifest
Normal file
10
repositories.manifest
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
: 1
|
||||||
|
summary: libcode-uri project repository
|
||||||
|
|
||||||
|
:
|
||||||
|
role: prerequisite
|
||||||
|
location: https://code.helloryan.se/code/libcode-validation.git##HEAD
|
||||||
|
|
||||||
|
:
|
||||||
|
role: prerequisite
|
||||||
|
location: https://code.helloryan.se/code/libcode-unicode.git##HEAD
|
8
tests/.gitignore
vendored
Normal file
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/}
|
Loading…
x
Reference in New Issue
Block a user