Code Monkey home page Code Monkey logo

minijson_reader's Introduction

minijson_reader

CMake

Motivation and design

When parsing JSON messages, most C/C++ libraries employ a DOM-based approach, i.e. they work by building an in-memory representation of object, and the client can then create/read/update/delete the properties of the object as needed, and most importantly access them in whatever order. While this is very convenient and provides maximum flexibility, there are situations in which memory allocations must or should preferably be avoided. minijson_reader is a callback-based parser, which can effectively parse JSON messages without allocating a single byte of memory on the heap, provided the input buffer can be modified.

minijson_writer is the independent counterpart for writing JSON messages.

Dependencies

minijson_reader is a single header file of ~1,500 LOC with no library dependencies. C++17 support is required.

Contexts

First of all, the client must create a context. A context contains the message to be parsed, plus other state the client should not be concerned about. Different context classes are currently available, corresponding to different ways of providing the input, different memory footprints, and different exception guarantees.

buffer_context

buffer_context can be used when the input buffer can be modified. It guarantees no memory allocations are performed, and consequently no std::bad_alloc exceptions will ever be thrown. Its constructor takes a pointer to a ASCII or UTF-8 encoded C string (not necessarily null-terminated) and the length of the string in bytes (not in UTF-8 characters).

char buffer[] = "{}";
minijson::buffer_context ctx(buffer, sizeof(buffer) - 1);
// ...

const_buffer_context

Similar to a buffer_context, but it does not modify the input buffer. const_buffer_context immediately allocates a buffer on the heap having the same size of the input buffer. It can throw std::bad_alloc only in the constructor, as no other memory allocations are performed after the object is created. The input buffer must stay valid for the entire lifetime of the const_buffer_context instance.

const char* buffer = "{}";
minijson::const_buffer_context ctx(buffer, strlen(buffer)); // may throw
// ...

istream_context

With istream_context the input is provided as a std::istream. The stream doesn't have to be seekable and will be read only once, one character at a time, until the end of the JSON message is reached, EOF is reached, or a parse error occurs. An arbitrary number of memory allocations may be performed upon construction and when the input is parsed with parse_object() or parse_array(), effectively changing the interface of those functions, that can throw std::bad_alloc when used with an istream_context.

// let input be a std::istream
minijson::istream_context ctx(input);
// ...

More about contexts

Contexts cannot be copied, but can be moved. Using a context that has been moved from causes undefined behavior.

Even if the context classes may have public methods, the client must not rely on them, as they may change without notice. The client-facing interface is limited to the constructor and the destructor.

The client can implement custom context classes, although the authors of this library do not yet provide a formal definition of a Context concept, which has to be reverse engineered from the source code, and can change without notice.

The same context cannot be used to parse more than one message, and cannot be reused after it is used for parsing a JSON message that causes a parse error: reusing contexts causes undefined behavior.

Parsing messages

parse_object and parse_array

A JSON object must be parsed with parse_object():

// let ctx be a context
minijson::parse_object(ctx, [&](std::string_view name, minijson::value value)
{
    // for every field...
});

name is a UTF-8 encoded string representing the name of the field. Its lifetime is that of the parsing context, except for buffer_context, in which case it will stay valid until the underlying input buffer is destroyed.

A JSON array must be parsed by using parse_array():

// let ctx be a context
minijson::parse_array(ctx, [&](minijson::value value)
{
    // for every element...
});

minijson::value represents the object field or array element value. Parsing nested objects or arrays, however, requires an explicit recursive call.

Both name and value can be safely copied, and all their copies will stay valid until the context is destroyed (or the underlying buffer is destroyed in case buffer_context is used).

The functor passed to parse_object() or parse_array() may optionally accept a context as the last parameter, in which case it will be passed the current parsing context, thus preventing the need to capture it inside the functor.

parse_object() and parse_array() return the number of bytes read from the input.

value

Object field and array element values are accessible through instances of the minijson::value class.

value has the following public methods:

  • minijson::value_type type(). The type of the value (String, Number, Boolean, Object, Array or Null). When the type of a value is Object or Array, you must parse the nested object or array by means of an explicit recursive call into parse_object() or parse_array() respectively.
  • template<typename T> T as(). The value as a T, where T is one of the following:
    • std::string_view. UTF-8 encoded string. This representation is available when type() is String; in all the other cases minijson::bad_value_cast is thrown. The lifetime of the returned std::string_view is that of the parsing context, except for buffer_context, in which case the std::string_view will stay valid until the underlying input buffer is destroyed.
    • bool. Only available when type() is Boolean; in all the other cases minijson::bad_value_cast is thrown.
    • arithmetic types (excluding bool). If type() is Number, the string representation of the value is contextually parsed by means of std::from_chars, and std::range_error is thrown in case the conversion fails because the value does not fit in the chosen arithmetic type. If type() is not Number, minijson::bad_value_cast is thrown.
    • std::optional<T>, where T is any of the above. The behavior is the same as for T, except that an empty optional is returned if and only if type() is Null. Exceptions caused by other failure modes are propagated.
  • template<typename T> T& to(T& dest). Shorthand for return (dest = as<T>());.
  • std::string_view raw(). The raw contents of the value. This method is useful for debugging or wrapping this library, but in general as() should be preferred. If type() is String, this method behaves just like as<std::string_view>(); if type() is Boolean, this method returns either true or false; if type() is Null, this method returns null; if type() is Number, this method returns the number exactly as it appears on the JSON message; if type() is Object or Array, this method returns an empty std::string_view. The lifetime of the returned std::string_view is that of the parsing context, except for buffer_context, in which case the std::string_view will stay valid until the underlying input buffer is destroyed.

The behavior of value::as() can be customized.

Parsing nested objects or arrays

When the type() of a value is Object or Array, you must parse the nested object or array by means of an explicit recursive call into parse_object() or parse_array() respectively, e.g.:

// let ctx be a context
minijson::parse_object(ctx, [&](std::string_view name, minijson::value value)
{
    // ...
    if (name == "...")
    {
        if (value.type() != minijson::Object)
        {
            throw some_exception("we were expecting an Object here");
        }
        minijson::parse_object(ctx, [&](std::string_view name, minijson::value value)
        {
           // parse the nested object
        });
    }
});

Ignoring nested objects and arrays

While all other fields and values can be simply ignored by omission, failing to parse a nested object or array will cause a parse error and consequently an exception to be thrown. You can properly ignore a nested object or array by calling minijson::ignore as follows:

// let ctx be a context
minijson::parse_object(ctx, [&](std::string_view name, minijson::value value)
{
    // ...
    if (name == "...")
    {
        if (value.type() != minijson::Object)
        {
            throw some_exception("we were expecting an Object here");
        }
        minijson::ignore(ctx); // proper way to ignore a nested object
    }
});

Simply passing an empty functor does not achieve the same result. minijson::ignore will recursively parse (and ignore) all the nested elements of the nested element itself (there is a protection against stack overflows: please refer to Parse errors to learn more). minijson::ignore is intended for nested objects and arrays, but does no harm if used to ignore elements of any other type.

Parse errors

parse_object() and parse_array() will throw a minijson::parse_error exception when something goes wrong.

parse_error provides a reason() method that returns a member of the parse_error::error_reason enum:

  • EXPECTED_OPENING_QUOTE
  • EXPECTED_UTF16_LOW_SURROGATE: learn more
  • INVALID_ESCAPE_SEQUENCE
  • UNESCAPED_CONTROL_CHARACTER
  • NULL_UTF16_CHARACTER
  • INVALID_UTF16_CHARACTER
  • INVALID_VALUE
  • EXPECTED_VALUE
  • UNTERMINATED_VALUE
  • EXPECTED_OPENING_BRACKET
  • EXPECTED_COLON
  • EXPECTED_COMMA_OR_CLOSING_BRACKET
  • NESTED_OBJECT_OR_ARRAY_NOT_PARSED: if this happens, make sure you are ignoring unnecessary nested objects or arrays in the proper way
  • EXCEEDED_NESTING_LIMIT: this means that the nesting depth exceeded a sanity limit that is defaulted to 32 and can be overriden at compile time by defining the MJR_NESTING_LIMIT macro. A sanity check on the nesting depth is essential to avoid stack overflows caused by malicious inputs such as [[[[[[[[[[[[[[[...more nesting...]]]]]]]]]]]]]]].

parse_error also has a size_t offset() method returning the approximate offset in the input message at which the error occurred. Beware: this offset is not guaranteed to be accurate, it can be out-of-bounds, and can change without prior notice in future versions of the library (for example, because it is made more accurate).

Customizing value::as()

You can extend the set of types value::as() can handle, or even override its behavior for some of the types supported by default, by specializing the minijson::value_as struct. For example:

enum class OrderType
{
    BUY,
    SELL,
};

namespace minijson
{

// Add support for your enum
template<>
struct value_as<OrderType>
{
    OrderType operator()(const value v) const
    {
        if (v.type() != String)
        {
            throw std::runtime_error(
                "could not convert JSON value to OrderType");
        }

        if (boost::iequals(v.raw(), "buy"))
        {
            return OrderType::BUY;
        }
        if (boost::iequals(v.raw(), "sell"))
        {
            return OrderType::SELL;
        }
        throw std::runtime_error("could not convert JSON value to OrderType");
    }
};

// *Override* the default behavior for floating point types
template<typename T>
struct value_as<T, std::enable_if_t<std::is_floating_point_v<T>>>
{
    T operator()(const value v) const
    {
        if (v.type() != Number)
        {
            throw std::runtime_error(
                "could not convert JSON value to a floating point number");
        }

        // Unclear why anyone would want to do this (it's just an example...)
        return boost::lexical_cast<T>(v.raw());

        // Note: you can always fall back to the default behavior like so:
        // return value_as_default<T>(v);
    }
};

} // namespace minijson

Dispatchers

While the arguments accepted by the functor passed to parse_object() allow for a great deal of control over how to process the fields, in a lot of straightforward cases they only force the client to write boilerplate such as:

// let ctx be a context
minijson::parse_object(ctx, [&](std::string_view name, minijson::value value)
{
   if (name == "field1") { /* do something */ }
   else if (name == "field2") { /* do something else */ }
   // ...
   else { /* unknown field, either ignore it or throw an exception */ }
});

Dispatchers can help factor out some of that boilerplate. This shows a typical usage example to get you started:

struct Order
{
    // To avoid memory allocations, here you could use a fixed-size string and
    // specialize struct value_as for it.
    std::string ticker;
    unsigned int price = 0;
    unsigned int size = 0;
    bool has_nyse = false;
    bool urgent = false;
};

using namespace minijson::handlers;
using minijson::value;

// It's advisable that dispatchers be singletons: do not create one each time
// you need to parse a message, it's not needed. Do make sure that your
// functors are completely stateless and thread-safe, though.
static const minijson::dispatcher dispatcher
{
    // These fields are *REQUIRED*. If they are missing, exceptions will
    // be thrown (more on that later...).
    handler("ticker", [](Order& o, value v) {v.to(o.ticker);}),
    handler("price", [](Order& o, value v) {v.to(o.price);}),
    handler("size", [](Order& o, value v) {v.to(o.size);}),

    // This field is optional
    optional_handler("urgent", [](Order& o, value v) {v.to(o.urgent);}),

    // This field will be handled but ignored if found: this is *NOT*
    // the same as providing an empty functor to optional_handler,
    // because it does the right thing to ignore objects and arrays!
    ignore_handler("sender"),

    // We want to handle this nested array in some special way...
    handler(
        "exchanges",
        // If you provide a 3-argument functor, the third argument is the
        // parsing context
        [](Order& o, value, auto& context)
        {
            parse_array(
                context,
                [&](value v)
                {
                    if (v.as<std::string_view>() == "NYSE")
                    {
                        o.has_nyse = true;
                    }
                });
        }),

    // An any_handler may choose to handle anything not previously handled,
    // or just reject it
    any_handler(
        [&](Order&, std::string_view name, value v)
        {
            if (name.find("debug-") == 0)
            {
                log_debug(v.as<int>());
                // We handled this field: no subsequent handlers will be called
                return true;
            }
            // Choose not to handle this field. Subsequent handlers will be
            // given a chance to handle it.
            return false;
        }),

    // This will ignore anything not previously handled, like a final catch-all
    // that sucks all unhandled fields into a black hole. When such handler
    // is not present, unhandled fields cause exceptions to be thrown
    // (more on that later...).
    ignore_any_handler {},
};

char buffer[] = R"json(
{
    "sender": {"source": "trader", "department": 1},
    "ticker": "ABCD",
    "price": 12,
    "size": 47,
    "exchanges": ["IEX", "NYSE"],
    "extended-debug-1": {"latency": 22},
    "debug-1": 42,
    "debug-2": -7
})json";
minijson::buffer_context context(buffer, sizeof(buffer));

Order order;
dispatcher.run(context, order);
// If run() has thrown no exceptions (which it can!), order is now populated

The above example illustrates the essentials. The following sections delve into the finer details.

Handlers

As seen in this example, minijson::dispatcher is constructed with an ordered list of handlers.

There are various types of handlers available in the minijson::handlers namespace:

  • handler(std::string_view field_name, Functor functor). Handles each occurrence of the required field named field_name by means of the provided functor, which has to accept the following arguments:

    1. The target(s), i.e. the object(s) being populated, if any
    2. The parsed value
    3. Optionally, the parsing context
  • optional_handler(std::string_view field_name, Functor functor). Handles each occurrence of the optional field named field_name by means of the provided functor, which has to accept the following arguments:

    1. The target(s), i.e. the object(s) being populated, if any
    2. The parsed value
    3. Optionally, the parsing context
  • any_handler(Functor functor). This handler is given the choice of handling potentially any field by means of the provided functor, which has to accept the following arguments:

    1. The target(s), i.e. the object(s) being populated, if any
    2. The field name as a std::string_view (lifetime tied to that of the parsing context)
    3. The parsed value
    4. Optionally, the parsing context

    and return something bool-convertible telling whether the field was handled or not. If the field was not handled, then the subsequent handlers will be given a chance to handle it.

  • ignore_handler(std::string_view field_name). Ignores the optional field named field_name.

  • any_ignore_handler(). Ignores any field. The only reasonable use for this handler is to place it as the very last one, to silently swallow and ignore any unknown fields.

The list of handlers used to construct a dispatcher define its type (via CTAD). It follows that the full type of a dispatcher cannot be reasonably spelled out (auto is your friend), and dispatchers cannot be stored in collections without somehow erasing their types.

Targets

A dispatcher runs against zero, one or more targets, which are the object(s) being populated with the contents of the JSON message. The targets, if any, will be passed to the handler functors (when applicable) as references, before the other arguments. It does not make much sense for the targets to be const, but it is allowed.

In this example, a dispatcher runs against multiple targets:

using namespace minijson::handlers;
using minijson::value;

static const minijson::dispatcher dispatcher
{
    handler("a", [&](int& a, int&, value v) {v.to(a);}),
    handler("b", [&](int&, int& b, value v) {v.to(b);}),
};

char buffer[] = R"json(
{
    "a": 1,
    "b": 2
})json";

minijson::buffer_context context(buffer, sizeof(buffer));
int a = 0;
int b = 0;
dispatcher.run(context, a, b);

Dispatch errors

The run() method of dispatcher calls parse_object(), which can throw parse_error exceptions.

Additionally, run() throws minijson::unhandled_field_error when no handler chooses to handle a parsed field.

Last, run() throws minijson::missing_field_error when a JSON message is parsed successfully, but does not contain a required field.

Both unhandled_field_error and missing_field_error have a std::string_view field_name_truncated() method returning the name of the field which caused the error, truncated at 55 characters. The lifetime of the returned std::string_view matches that of the exception (it is backed by a fixed-length array inside the exception object, thus avoiding dangling references as well as memory allocations).

In case multiple required fields are missing from the JSON message being parsed, it is unspecified which one is returned by missing_field_error::field_name_truncated(). Refer to the next section if your application requires more control.

Advanced dispatchers use with dispatcher_run

Going back to this example, the last two lines are in fact a shorthand for:

Order order;
minijson::dispatcher_run run(dispatcher, order);
minijson::parse_object(context, run);
run.enforce_required(); // throws missing_field_error

Using a minijson::dispatcher_run instance after the underlying dispatcher has been destroyed causes undefined behavior. Passing the same dispatcher_run instance to parse_object() more than once causes unspecified behavior.

Holding on to a dispatcher_run instance gives you greater control over how errors are handled. Indeed, you can omit the call to enforce_required() and optionally replace it with your custom validation logic by calling inspect() instead:

run.inspect(
    [](const auto& handler, const std::size_t handle_count)
    {
        // For each handler of the dispatcher (in the order they are listed),
        // this functor will be passed the handler instance (`handler`) and the
        // number of times that handler has decided to handle a field
        // (`handle_count`).
        if constexpr (handlers::traits<decltype(handler)>::is_required_field)
        {
            if (handle_count == 0)
            {
                std::cout
                    << "We're missing " << handler.field_name() << " "
                    << "but that's OK, we won't throw, maybe we can log "
                    << "something or add the field name to a collection";
            }
        }
    });

In the example above, is_required_field is a handler trait, a compile-time bool constant allowing you to determine what kind of handler you are dealing with. The following traits are available under minijson::handlers::traits<Handler>:

Trait Description true for the handlers
is_field_specific Handles each occurrence of a specific field handler, optional_handler, ignore_handler
is_ignore Ignores fields ignore_handler, any_ignore_handler
is_required_field Handles a required field handler

Handlers for which is_field_specific is true have a field_name() method returning a std::string_view the lifetime of which is tied to that of the handler, which in turn depends on the lifetime of the underlying dispatcher.

You can reverse engineer how handlers are implemented to roll your very own, and as long as you expose the correct interface (including the traits listed above) it should "just work", but the authors of this library do not yet provide a formal definition of a Handler concept, which can change without notice.

minijson_reader's People

Contributors

giacomodrago avatar solace-mkourlas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

minijson_reader's Issues

build errors on OSX

cmake -S . -B ./cmake-build-minijson_reader_CK-x86_64-Debug/install -G Ninja -D CMAKE_CXX_COMPILER_LAUNCHER=/usr/local/bin/ccache -D CMAKE_EXPORT_COMPILE_COMMANDS=1 -D CMAKE_BUILD_TYPE=Debug -D CMAKE_PREFIX_PATH=/Users/clausklein/Workspace/cpp/minijson_reader_CK/stage -D BUILD_TESTING=1 -D CMAKE_CXX_STANDARD=17 -D CMAKE_INSTALL_PREFIX=/Users/clausklein/Workspace/cpp/minijson_reader_CK/stage
-- The C compiler identification is AppleClang 14.0.0.14000029
-- The CXX compiler identification is AppleClang 14.0.0.14000029
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/usr/bin/gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/usr/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found GTest: /usr/local/lib/cmake/GTest/GTestConfig.cmake (found version "1.12.1")  
-- Configuring done
-- Generating done
CMake Warning:
  Manually-specified variables were not used by the project:

    BUILD_TESTING


-- Build files have been written to: /Users/clausklein/Workspace/cpp/minijson_reader_CK/cmake-build-minijson_reader_CK-x86_64-Debug/install
+ cmake --build ./cmake-build-minijson_reader_CK-x86_64-Debug/install
[1/6] Building CXX object CMakeFiles/test_value_as.dir/test/value_as.cpp.o
FAILED: CMakeFiles/test_value_as.dir/test/value_as.cpp.o 
/usr/local/bin/ccache /Applications/Xcode.app/Contents/Developer/usr/bin/g++  -I/Users/clausklein/Workspace/cpp/minijson_reader_CK -isystem /usr/local/include -g -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -std=c++17 -MD -MT CMakeFiles/test_value_as.dir/test/value_as.cpp.o -MF CMakeFiles/test_value_as.dir/test/value_as.cpp.o.d -o CMakeFiles/test_value_as.dir/test/value_as.cpp.o -c /Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp
In file included from /Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:26:
/Users/clausklein/Workspace/cpp/minijson_reader_CK/minijson_reader.hpp:1102:45: error: call to deleted function 'from_chars'
            const auto [parse_end, error] = std::from_chars(begin, end, result);
                                            ^~~~~~~~~~~~~~~
/Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:105:16: note: in instantiation of function template specialization 'minijson::value_as_default<double>' requested here
        return value_as_default<T>(v);
               ^
/Users/clausklein/Workspace/cpp/minijson_reader_CK/minijson_reader.hpp:1002:16: note: in instantiation of member function 'minijson::value_as<double>::operator()' requested here
        return value_as<T>()(*this);
               ^
/Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:135:21: note: in instantiation of function template specialization 'minijson::value::as<double>' requested here
    ASSERT_EQ(42, v.as<double>());
                    ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/charconv:167:6: note: candidate function has been explicitly deleted
void from_chars(const char*, const char*, bool, int = 10) = delete;
     ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/charconv:664:1: note: candidate template ignored: requirement 'is_integral<double>::value' was not satisfied [with _Tp = double]
from_chars(const char* __first, const char* __last, _Tp& __value)
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/charconv:671:1: note: candidate function template not viable: requires 4 arguments, but 3 were provided
from_chars(const char* __first, const char* __last, _Tp& __value, int __base)
^
In file included from /Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:26:
/Users/clausklein/Workspace/cpp/minijson_reader_CK/minijson_reader.hpp:1102:24: error: variable has incomplete type 'const void'
            const auto [parse_end, error] = std::from_chars(begin, end, result);
                       ^
/Users/clausklein/Workspace/cpp/minijson_reader_CK/minijson_reader.hpp:1102:45: error: call to deleted function 'from_chars'
            const auto [parse_end, error] = std::from_chars(begin, end, result);
                                            ^~~~~~~~~~~~~~~
/Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:105:16: note: in instantiation of function template specialization 'minijson::value_as_default<float>' requested here
        return value_as_default<T>(v);
               ^
/Users/clausklein/Workspace/cpp/minijson_reader_CK/minijson_reader.hpp:1002:16: note: in instantiation of member function 'minijson::value_as<float>::operator()' requested here
        return value_as<T>()(*this);
               ^
/Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:136:21: note: in instantiation of function template specialization 'minijson::value::as<float>' requested here
    ASSERT_EQ(42, v.as<float>());
                    ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/charconv:167:6: note: candidate function has been explicitly deleted
void from_chars(const char*, const char*, bool, int = 10) = delete;
     ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/charconv:664:1: note: candidate template ignored: requirement 'is_integral<float>::value' was not satisfied [with _Tp = float]
from_chars(const char* __first, const char* __last, _Tp& __value)
^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/charconv:671:1: note: candidate function template not viable: requires 4 arguments, but 3 were provided
from_chars(const char* __first, const char* __last, _Tp& __value, int __base)
^
In file included from /Users/clausklein/Workspace/cpp/minijson_reader_CK/test/value_as.cpp:26:
/Users/clausklein/Workspace/cpp/minijson_reader_CK/minijson_reader.hpp:1102:24: error: variable has incomplete type 'const void'
            const auto [parse_end, error] = std::from_chars(begin, end, result);
                       ^
4 errors generated.
[2/6] Building CXX object CMakeFiles/test_main.dir/test/main.cpp.o
FAILED: CMakeFiles/test_main.dir/test/main.cpp.o 
# . . .
bash-3.2$ git log -1
commit 632e7221cf4cf3324988b5b7f050d7b9f1bf19b9 (HEAD -> feature/fix-build-on-osx, origin/master, claus/master, master)
Author: giacomodrago <[email protected]>
Date:   Mon Jul 4 22:46:04 2022 +0100

    Update codeql-analysis.yml
bash-3.2$ 

parse_object issue

I did an easy test to parse_object() on an json like: "{ "firstName":"John" , "lastName":"Doe" }".
it gives me a exception.
turns out that change the line 144, of minijson_read.hpp from "m_read_offset++" to "++m_read_offset" will solve this ,
or should I just use a json string start with "\n"?

C++17 features

Hello,

I would like to check possibility of using minijson_reader for C++14 as QNX compiler I am using supports upto C++14 considering functional safety. Can you please let me know which particular feature of C++17 is being used in the header file? Moreover, I also want to know whether minijson_reader code can be used in ASIL B Application since it's not using any dynamic memory?

Many thanks!

Regards,
Hemal Bavishi

License change request

I wanted to thank you for making such a useful project. I have recently started using this project at work.

I was wondering if you would be willing to change the license to be one of the standard open source licenses? I think the MIT license would most closely match your existing license. Doing so would make it easier for me to use it at work.

Dispatcher example in README does not compile

From README.md:

struct Order
{
    // (comment)
    std::string ticker;
    handler("ticker", [](Order& o, value v) {v.to(o.ticker);}),

Attempting to compile this code causes value::as<std::string> to fail because std::string_view must be used instead. Indeed, Order::ticker is an std::string_view in test/dispatcher.cpp.

Unable to read arrays

I am trying using this project to read a json file which looks like that:

{
	'version': n,
        'capabilities': ['...'],
	'rules': ['rulename'],
	'vbavailable': {
		'rulename': bitnumber
	},
	'vbrequired': n,
	'previousblockhash': 'xxxx',
	'transactions': [{
		'data': 'xxxx',
		'txid': 'xxxx',
		'hash': 'xxxx',
		'depends': [n],
		'fee': n,
		'sigops': n,
		'weight': n,
	}],
	'coinbaseaux': {
		'flags': 'xx'
	},
	'coinbasevalue': n,
	'coinbasetxn': {},
	'target': 'xxxx',
	'mintime': xxx,
	'mutable': ['value'],
	'noncerange': '00000000ffffffff',
	'sigoplimit': n,
	'sizelimit': n,
	'weightlimit': n,
	'curtime': ttt,
	'bits': 'xxxxxxxx',
	'height': n
}

my code looks like this right now:

      buffer_context ctx(chunk.memory, chunk.size);
      try {
        parse_object(ctx, [&](const char* k, value v) {
          dispatch (k)
          <<"result">> [&]() {
            try {
              parse_object(ctx, [&](const char* k, value v) {
                dispatch (k)
                <<"version">> [&]() { result.setVersion(v.as_long()); }
                <<"capabilities">> [&]()
                {
                    parse_array(ctx, [&](value capability)
                    {
                        result.getCapabilities().push_back(capability.as_string());
                    });
                }
...

the value from fields like 'version' or similars are read fine, but with arrays I got no data on them. I try view the data this way:

  cout << "version: " << bt.getVersion() << endl;
  cout << "capabilities (size): " << bt.getCapabilities().size() << endl;

and I always got size 0 for the arrays, even when the json has data. Can I get any hint of how to solve this issue or how to get more data for fing the issue? I try visualize the data putting this code:

cout << capability.as_string() << endl;

inside the parse_array method, and I got the correct values, but still the data is not persisted on the vector used to store the data.

Determine if toplevel content is object or array

If the top-level JSON content that I'm trying to parse can be either an array or an object, how do I tell which one it is? For nested content, I would use value::type(), but for the top-level content I don't have a value to call that on.

Rework dispatch

At the moment minijson::dispatch is a bit of a mess, not to mention a sheer abuse of operator overloading.

I would like to do something better (still under the constraint that it shouldn't allocate on the heap): ideally it should be able to (optionally) detect missing and/or unexpected fields and throw.

minijson_reader does not distinguish between an unparsable value and a value parsable as zero

minijson_reader provides long and double representations of JSON numbers:

long as_long(): the value as a long integer. This representation is available when type() is Number and the number could be parsed by strtol without overflows, or when the type is Boolean, in which case 1 or 0 are returned for true and false respectively. In all the other cases, 0 is returned.

double as_double(): the value as a double-precision floating-point number. This representation is available when type() is Number and the number could be parsed by strtod without overflows or underflows, or when the type is Boolean, in which case non-zero or 0.0 are returned for true and false respectively. In all the other cases, 0.0 is returned.

The problem is that there’s no way to distinguish between 0 and “this value could not be parsed”.

I know there's a broader rework of number parsing in #4 being discussed, but as a short-term fix we can add long_representation_available and double_representation_available booleans.

Rework number parsing

In parse_double(...) the value is parsed with std::strtod. This function uses the current locale, for which the separator character can be ',' instead of '.'.

This is an issue for JSON files created in the US and parsed in foreign countries.

One way to fix this problem is to set the thread locale to en-US before calling parse_object(...). However, this creates problems if the same thread displays messages, write files, etc. within parsing handlers.

Another option is to add set_locale() and get_locale() methods to buffer_context_base, a m_locale member initialized to the current locale, and use this in strtod.

Small compilation errors

In commit 5ea1e3: Line 177 and 187: buffer_context_base should be detail::buffer_context_base
Line 468 and 504: strtol and strtod should be std::strtol and std::strtod respectively.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.