Skip to content

Commit

Permalink
Merge pull request #132 from CrowCpp/quality_of_life
Browse files Browse the repository at this point in the history
Quality of life improvements
  • Loading branch information
The-EDev committed May 13, 2021
2 parents 301e535 + 0e9b615 commit 361023b
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 31 deletions.
33 changes: 21 additions & 12 deletions docs/guides/json.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
Crow has built in support for JSON data.<br><br>

##type
The types of values that `rvalue and wvalue` can take are as follows:<br>

- `False`: from type `bool`.
- `True`: from type `bool`.
- `Number`
- `Floating_point`: from type `double`.
- `Signed_integer`: from type `int`.
- `Unsigned_integer`: from type `unsigned int`.
- `String`: from type `std::string`.
- `List`: from type `std::vector`.
- `Object`: from type `crow::json::wvalue or crow::json::rvalue`.<br>
This last type means that `rvalue or wvalue` can have keys.

##rvalue
JSON read value, used for taking a JSON string and parsing it into `crow::json`.<br><br>

Expand All @@ -11,19 +25,14 @@ For more info on read values go [here](/reference/classcrow_1_1json_1_1rvalue.ht
#wvalue
JSON write value, used for creating, editing and converting JSON to a string.<br><br>

The types of values that `wvalue` can take are as follows:<br>
!!!note

- `False`: from type `bool`.
- `True`: from type `bool`.
- `Number`
- `Floating_point`: from type `double`.
- `Signed_integer`: from type `int`.
- `Unsigned_integer`: from type `unsigned int`.
- `String`: from type `std::string`.
- `List`: from type `std::vector`.
- `Object`: from type `crow::json::wvalue`.<br>
This last type means that `wvalue` can have keys, this is done by simply assigning a value to whatever string key you like, something like `#!cpp wval["key1"] = val1;`. Keep in mind that val1 can be any of the above types.<br><br>
setting a `wvalue` to object type can be done by simply assigning a value to whatever string key you like, something like `#!cpp wval["key1"] = val1;`. Keep in mind that val1 can be any of the above types.

A `wvalue` can be treated as an object or even a list (setting a value by using `json[3] = 32` for example). Please note that this will remove the data in the value if it isn't of List type.<br><br>

A JSON wvalue can be returned directly inside a route handler, this will cause the `content-type` header to automatically be set to `Application/json` and the JSON value will be converted to string and placed in the response body. For more information go to [Routes](../routes).<br><br>
An object type `wvalue` uses `std::unordered_map` by default, if you want to have your returned `wvalue` key value pairs be sorted (using `std::map`) you can add `#!cpp #define CROW_JSON_USE_MAP` to the top of your program.<br><br>

A JSON `wvalue` can be returned directly inside a route handler, this will cause the `content-type` header to automatically be set to `Application/json` and the JSON value will be converted to string and placed in the response body. For more information go to [Routes](../routes).<br><br>

For more info on write values go [here](../../reference/classcrow_1_1json_1_1wvalue.html).
29 changes: 29 additions & 0 deletions docs/guides/query-string.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
A query string is the part of the url that comes after a `?` character, it is usually formatted as `key=value&otherkey=othervalue`.
<br><br>

Crow supports query strings through `crow::request::url_params`. The object is of type `crow::query_string` and can has the following functions:<br>
##get(name)
Returns the value (as char*) based on the given key (or name). Returns `nullptr` if the key is not found.
##pop(name)
Works the same as `get`, but removes the returned value.
!!! note

`crow::request::url_params` is a const value, therefore for pop (also pop_list and pop_dict) to work, a copy needs to be made.

##get_list(name)
A url can be `http://example.com?key[]=value1&key[]=value2&key[]=value3`. Using `get_list("key")` on such a url returns an `std::vector<std::string>` containing `[value1, value2, value3]`.<br><br>

`#!cpp get_list("key", false)` can be used to parse `http://example.com?key=value1&key=value2&key=value3`
##pop_list(name)
Works the same as `get_list` but removes all instances of values having the given key (`use_brackets` is also available here).
##get_dict(name)
Returns an `std::unordered_map<std::string, std::string>` from a query string such as `?key[sub_key1]=value1&key[sub_key2]=value2&key[sub_key3]=value3`.<br>
The key in the map is what's in the brackets (`sub_key1` for example), and the value being what's after the `=` sign (`value1`). The name passed to the function is not part of the returned value.
##pop_dict(name)
Works the same as `get_dict` but removing the values from the query string.
!!!warning

if your query string contains both a list and dictionary with the same key, it is best to use `pop_list` before either `get_dict` or `pop_dict`, since a map cannot contain more than one value per key, each item in the list will override the previous and only the last will remain with an empty key.

<br><br>
For more information take a look [here](../../reference/classcrow_1_1query__string.html).
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ else ()
add_executable(example_catchall example_catchall.cpp)
target_compile_options(example_catchall PRIVATE "${compiler_options}")
target_link_libraries(example_catchall PUBLIC ${REQUIRED_LIBRARIES})

add_executable(example_json_map example_json_map.cpp)
target_compile_options(example_json_map PRIVATE "${compiler_options}")
target_link_libraries(example_json_map PUBLIC ${REQUIRED_LIBRARIES})

add_custom_command(OUTPUT example_chat.html
COMMAND ${CMAKE_COMMAND} -E
Expand Down
28 changes: 28 additions & 0 deletions examples/example_json_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#define CROW_MAIN
#define CROW_JSON_USE_MAP
#include "crow.h"



int main()
{
crow::SimpleApp app;

// simple json response using a map
// To see it in action enter {ip}:18080/json
// it shoud show amessage before zmessage despite adding zmessage first.
CROW_ROUTE(app, "/json")
([]{
crow::json::wvalue x;
x["zmessage"] = "Hello, World!";
x["amessage"] = "Hello, World2!";
return x;
});

// enables all log
app.loglevel(crow::LogLevel::Debug);

app.port(18080)
.multithreaded()
.run();
}
140 changes: 132 additions & 8 deletions include/crow/json.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#pragma once

//#define CROW_JSON_NO_ERROR_CHECK
//#define CROW_JSON_USE_MAP

#include <string>
#ifdef CROW_JSON_USE_MAP
#include <map>
#else
#include <unordered_map>
#endif
#include <iostream>
#include <algorithm>
#include <memory>
Expand Down Expand Up @@ -295,6 +300,28 @@ namespace crow
return static_cast<int>(i());
}

///Return any json value (not object or list) as a string.
explicit operator std::string() const
{
#ifndef CROW_JSON_NO_ERROR_CHECK
if (t() == type::Object || t() == type::List)
throw std::runtime_error("json type container");
#endif
switch (t())
{
case type::String:
return std::string(s());
case type::Null:
return std::string("null");
case type::True:
return std::string("true");
case type::False:
return std::string("false");
default:
return std::string(start_, end_-start_);
}
}

/// The type of the JSON value.
type t() const
{
Expand Down Expand Up @@ -382,6 +409,22 @@ namespace crow
return detail::r_string{start_, end_};
}

///The list or object value
std::vector<rvalue> lo()
{
#ifndef CROW_JSON_NO_ERROR_CHECK
if (t() != type::Object && t() != type::List)
throw std::runtime_error("value is not a container");
#endif
std::vector<rvalue> ret;
ret.reserve(lsize_);
for (uint32_t i = 0; i<lsize_; i++)
{
ret.emplace_back(l_[i]);
}
return ret;
}

/// Convert escaped string character to their original form ("\\n" -> '\n').
void unescape() const
{
Expand Down Expand Up @@ -448,6 +491,7 @@ namespace crow
}
}

///Check if the json object has the passed string as a key.
bool has(const char* str) const
{
return has(std::string(str));
Expand Down Expand Up @@ -590,6 +634,21 @@ namespace crow
{
return (option_&error_bit)!=0;
}

std::vector<std::string> keys()
{
#ifndef CROW_JSON_NO_ERROR_CHECK
if (t() != type::Object)
throw std::runtime_error("value is not an object");
#endif
std::vector<std::string> ret;
ret.reserve(lsize_);
for (uint32_t i = 0; i<lsize_; i++)
{
ret.emplace_back(std::string(l_[i].key()));
}
return ret;
}
private:
bool is_cached() const
{
Expand Down Expand Up @@ -1091,6 +1150,7 @@ namespace crow
}

// TODO caching key to speed up (flyweight?)
// I have no idea how flyweight could apply here, but maybe some speedup can happen if we stopped checking type since decode_string returns a string anyway
auto key = t.s();

ws_skip();
Expand Down Expand Up @@ -1176,10 +1236,25 @@ namespace crow
} num; ///< Value if type is a number.
std::string s; ///< Value if type is a string.
std::unique_ptr<std::vector<wvalue>> l; ///< Value if type is a list.
#ifdef CROW_JSON_USE_MAP
std::unique_ptr<std::map<std::string, wvalue>> o;
#else
std::unique_ptr<std::unordered_map<std::string, wvalue>> o; ///< Value if type is a JSON object.
#endif

public:

wvalue() : returnable("application/json") {}

wvalue(std::vector<wvalue>& r) : returnable("application/json")
{
t_ = type::List;
l = std::unique_ptr<std::vector<wvalue>>(new std::vector<wvalue>{});
l->reserve(r.size());
for(auto it = r.begin(); it != r.end(); ++it)
l->emplace_back(*it);
}

/// Create a write value from a read value (useful for editing JSON strings).
wvalue(const rvalue& r) : returnable("application/json")
{
Expand Down Expand Up @@ -1209,16 +1284,55 @@ namespace crow
l->emplace_back(*it);
return;
case type::Object:
o = std::unique_ptr<
std::unordered_map<std::string, wvalue>
>(
new std::unordered_map<std::string, wvalue>{});
#ifdef CROW_JSON_USE_MAP
o = std::unique_ptr<std::map<std::string, wvalue>>(new std::map<std::string, wvalue>{});
#else
o = std::unique_ptr<std::unordered_map<std::string, wvalue>>(new std::unordered_map<std::string, wvalue>{});
#endif
for(auto it = r.begin(); it != r.end(); ++it)
o->emplace(it->key(), *it);
return;
}
}

wvalue(const wvalue& r) : returnable("application/json")
{
t_ = r.t();
switch(r.t())
{
case type::Null:
case type::False:
case type::True:
return;
case type::Number:
nt = r.nt;
if (nt == num_type::Floating_point)
num.d = r.num.d;
else if (nt == num_type::Signed_integer)
num.si = r.num.si;
else
num.ui = r.num.ui;
return;
case type::String:
s = r.s;
return;
case type::List:
l = std::unique_ptr<std::vector<wvalue>>(new std::vector<wvalue>{});
l->reserve(r.size());
for(auto it = r.l->begin(); it != r.l->end(); ++it)
l->emplace_back(*it);
return;
case type::Object:
#ifdef CROW_JSON_USE_MAP
o = std::unique_ptr<std::map<std::string, wvalue>>(new std::map<std::string, wvalue>{});
#else
o = std::unique_ptr<std::unordered_map<std::string, wvalue>>(new std::unordered_map<std::string, wvalue>{});
#endif
o->insert(r.o->begin(), r.o->end());
return;
}
}

wvalue(wvalue&& r) : returnable("application/json")
{
*this = std::move(r);
Expand Down Expand Up @@ -1421,10 +1535,11 @@ namespace crow
reset();
t_ = type::Object;
if (!o)
o = std::unique_ptr<
std::unordered_map<std::string, wvalue>
>(
new std::unordered_map<std::string, wvalue>{});
#ifdef CROW_JSON_USE_MAP
o = std::unique_ptr<std::map<std::string, wvalue>>(new std::map<std::string, wvalue>{});
#else
o = std::unique_ptr<std::unordered_map<std::string, wvalue>>(new std::unordered_map<std::string, wvalue>{});
#endif
return (*o)[str];
}

Expand All @@ -1440,6 +1555,15 @@ namespace crow
return result;
}

/// If the wvalue is a list, it returns the length of the list, otherwise it returns 1.
std::size_t size() const
{
if (t_ != type::List)
return 1;
return l->size();
}

/// Returns an estimated size of the value in bytes.
size_t estimate_length() const
{
switch(t_)
Expand Down
Loading

0 comments on commit 361023b

Please sign in to comment.