Current section
Files
Jump to
Current section
Files
c_src/simdjson_decoder.hpp
#pragma once
#include <erl_nif.h>
#include <utility>
#include <unordered_set>
#include "simdjson.h"
#include "simdjson_atoms.hpp"
#include "simdjson_bigint.hpp"
#if __cplusplus < 202101L
namespace std {
[[noreturn]] inline void unreachable()
{
// Uses compiler specific extensions if possible.
// Even if no extension is used, undefined behavior is still raised by
// an empty function body and the noreturn attribute.
#if defined(_MSC_VER) && !defined(__clang__) // MSVC
__assume(false);
#else // GCC, Clang
__builtin_unreachable();
#endif
}
}
#endif
namespace simdjsone {
struct DecodeOpts {
DecodeOpts()
: return_maps(true)
, null_term(am_null)
, dedupe_keys(false)
{}
bool return_maps;
ERL_NIF_TERM null_term;
bool dedupe_keys;
};
using namespace simdjson;
struct OnDemandDecoder {
OnDemandDecoder(ErlNifEnv* env, const DecodeOpts& opts);
inline ERL_NIF_TERM to_json(ErlNifBinary const& bin);
private:
template <typename T>
std::pair<bool, ERL_NIF_TERM> decode_number(T& doc) noexcept;
std::pair<bool, ERL_NIF_TERM> decode_number(ondemand::number num) noexcept;
inline ERL_NIF_TERM unescape_string(ondemand::raw_json_string rjs, size_t size,
std::unordered_set<std::string>* seen, ERL_NIF_TERM& err);
inline std::pair<bool, ERL_NIF_TERM> recursive_processor(ondemand::value element);
void release_binaries(std::vector<ERL_NIF_TERM>& items);
inline ERL_NIF_TERM raise_error(ERL_NIF_TERM, const char* err);
ondemand::parser m_parser;
ErlNifEnv* m_env;
DecodeOpts m_opts;
padded_string m_buff;
};
OnDemandDecoder::OnDemandDecoder(ErlNifEnv* env, const DecodeOpts& opts)
: m_env(env)
, m_opts(opts)
{}
ERL_NIF_TERM OnDemandDecoder::to_json(ErlNifBinary const& bin) {
padded_string json(reinterpret_cast<const char*>(bin.data), bin.size);
auto buf = padded_string(bin.size);
m_buff.swap(buf);
ondemand::document doc = m_parser.iterate(json);
ERL_NIF_TERM res, errcode = 0;
if (doc.is_scalar()) {
// we have a special case where the JSON document is a single document...
switch (doc.type()) {
case ondemand::json_type::number:
res = decode_number(doc).second;
break;
case ondemand::json_type::string:
res = unescape_string(doc.get_raw_json_string(), doc.raw_json_token().value_unsafe().size(), nullptr, errcode);
if (errcode) [[unlikely]] return raise_error(errcode, "Failed to decode string");
assert(res);
break;
case ondemand::json_type::boolean:
res = doc.get_bool() ? AM_TRUE : AM_FALSE;
break;
case ondemand::json_type::null:
// We check that the value is indeed null
// otherwise: an error is thrown.
res = doc.is_null() ? m_opts.null_term : raise_error(AM_ERROR, "NULL expected");
break;
case ondemand::json_type::array:
case ondemand::json_type::object:
default:
// This is impossible
res = raise_error(AM_ERROR, "Unreachable code");
}
} else {
ondemand::value val = doc;
res = recursive_processor(val).second;
}
assert(res);
if (!doc.at_end())
return raise_error(AM_BADARG, "Unexpectedly tokens after the end of the json");
return res;
}
std::pair<bool, ERL_NIF_TERM>
OnDemandDecoder::recursive_processor(ondemand::value element)
{
switch (element.type()) {
case ondemand::json_type::array: {
std::vector<ERL_NIF_TERM> items;
for (auto child : element.get_array()) {
auto res = recursive_processor(child.value());
if (!res.first) [[unlikely]] {
release_binaries(items);
return res;
}
items.push_back(res.second);
}
return std::make_pair(true, enif_make_list_from_array(m_env, items.data(), items.size()));
}
case ondemand::json_type::object: {
std::vector<ERL_NIF_TERM> keys;
std::vector<ERL_NIF_TERM> vals;
std::unordered_set<std::string> seen;
ERL_NIF_TERM errcode;
auto pseen = m_opts.dedupe_keys ? &seen : nullptr;
for (auto field : element.get_object()) {
// TODO: add error checking for field.key?
auto k = unescape_string(field.key(), field.key_raw_json_token().value_unsafe().size(), pseen, errcode);
if (errcode) [[unlikely]]
goto ERR;
if (!k) [[unlikely]]
continue;
keys.push_back(k);
auto res = recursive_processor(field.value());
if (!res.first) [[unlikely]] {
release_binaries(keys);
release_binaries(vals);
return res;
}
vals.push_back(res.second);
}
ERL_NIF_TERM m;
if (m_opts.return_maps) {
if (!enif_make_map_from_arrays(m_env, keys.data(), vals.data(), keys.size(), &m)) [[unlikely]] {
errcode = AM_DUP_KEYS_FOUND;
goto ERR;
}
} else {
std::vector<ERL_NIF_TERM> v;
v.resize(keys.size());
for (size_t i=0; i < v.size(); ++i)
v[i] = enif_make_tuple2(m_env, keys[i], vals[i]);
m = enif_make_list_from_array(m_env, v.data(), v.size());
m = enif_make_tuple1(m_env, m);
}
return std::make_pair(true, m);
ERR:
release_binaries(keys);
release_binaries(vals);
return std::make_pair(false, enif_raise_exception(m_env, errcode));
}
case ondemand::json_type::number:
return decode_number(element);
case ondemand::json_type::string: {
ERL_NIF_TERM err = 0;
auto s = unescape_string(element.get_raw_json_string(), element.raw_json_token().size(), nullptr, err);
if (err) [[unlikely]] return std::make_pair(false, raise_error(AM_ENOMEM, "Not enough memory"));
assert(s);
return std::make_pair(true, s);
}
case ondemand::json_type::boolean: {
bool val = false;
auto err = element.get(val);
return std::make_pair(err == SUCCESS, val ? AM_TRUE : AM_FALSE);
}
case ondemand::json_type::null:
// We check that the value is indeed null
// otherwise: an error is thrown.
return element.is_null()
? std::make_pair(true, m_opts.null_term)
: std::make_pair(false, raise_error(AM_NULL, "Invalid NULL"));
default:
return std::make_pair(false, raise_error(AM_ERROR, "Unhandled switch clause"));
}
}
template <typename T>
std::pair<bool, ERL_NIF_TERM>
OnDemandDecoder::decode_number(T& val) noexcept {
auto res = val.get_number();
if (res.error() != SUCCESS) [[unlikely]] {
std::string_view str = val.raw_json();
auto val = BigInt::decode(m_env, str.begin(), str.end());
return std::make_pair(val != 0, val ? val : AM_EBIGINT);
}
return decode_number(res.value_unsafe());
}
std::pair<bool, ERL_NIF_TERM>
OnDemandDecoder::decode_number(ondemand::number num) noexcept {
using ondemand::number_type;
switch (num.get_number_type()) {
case number_type::floating_point_number:
return std::make_pair(true, enif_make_double(m_env, num.get_double()));
case number_type::signed_integer:
return std::make_pair(true, enif_make_int64(m_env, num.get_int64()));
case number_type::unsigned_integer:
return std::make_pair(true, enif_make_uint64(m_env, num.get_uint64()));
case number_type::big_integer:
// This should be handled by the "if" clause in the beginning of this function
return std::make_pair(false, raise_error(AM_ERROR, "Unexpected number type"));
default:
std::unreachable();
}
}
inline ERL_NIF_TERM OnDemandDecoder::
unescape_string(ondemand::raw_json_string in, size_t size, std::unordered_set<std::string>* seen, ERL_NIF_TERM& err) {
auto dst = reinterpret_cast<uint8_t*>(m_buff.data());
std::string_view v = m_parser.unescape(in, dst);
if (seen) {
auto [it, ok] = seen->insert(std::string(v));
if (!ok) [[unlikely]]
return err = 0;
}
err = 0;
return make_binary(m_env, v);
}
ERL_NIF_TERM OnDemandDecoder::raise_error(ERL_NIF_TERM reason, const char* err)
{
return ::raise_error(m_env, reason, err);
}
void OnDemandDecoder::release_binaries(std::vector<ERL_NIF_TERM>& items)
{
for (auto t : items)
if (enif_is_binary(m_env, t)) {
ErlNifBinary bin;
enif_inspect_binary(m_env, t, &bin);
enif_release_binary(&bin);
}
}
} // namespace simdjsone