Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to read portion object from unknow JSON string? #147

Closed
sweihub opened this issue Jan 31, 2023 · 4 comments
Closed

How to read portion object from unknow JSON string? #147

sweihub opened this issue Jan 31, 2023 · 4 comments

Comments

@sweihub
Copy link

sweihub commented Jan 31, 2023

Hi

I am reading through the README, however, I do not find an official way to read portion object from JSON string. The JSON pointer syntax dose not read a key from a unknow JSON message.

The common use case is to implement a API, we need to decide the message type, and then deserialize, for example

{
    "action": "PUT",
    "data": {
        "x": 100,
        "y": 200
    }
}

Expected way

std::string action = glz::read_json_whatever<std::string>("/path/to/action",  buffer);
if (action == "DELETE") {
    // Now we know the message type
    auto bomb = glz::read_json<bomb_t>(buffer);
}

Workaround (simdjson)

Using the simdjson on-demand API, I hope glaze can also have this.

#include <iostream>
#include "simdjson.h"

using namespace simdjson;

int main(void) {
    ondemand::parser parser;
    std::string_view action;

    std::string buffer = R"( { "action": "GET", "data": { "x": 10, "y": 200 }})";
    auto json = padded_string(buffer);
    auto request = parser.iterate(json);
    auto error = request.at_pointer("/action").get_string().get(action);

    if (action == "GET") {
        printf("TODO: deserialize with glaze as GET\n");
    }

    return 0;
}
@mwalcott3
Copy link
Collaborator

mwalcott3 commented Jan 31, 2023

We could add a way of parsing a single json value quicker by skipping portions of the document we are not interested in but I see this as more of a type selection problem. There is limited support for handling this right now https://github.com/stephenberry/glaze/wiki/Variant-Handling. We really need to have a better way of generically handling variants and type selection but don't want to rush the solution and end up stuck with a bad one.

As a workaround, you could do a 2 stage parse using a generic parse and then a typed parse. This is very slow since it involves an extra unnecessary parse and quite a bit of dynamic memory allocations.

std::string buffer = R"( { "action": "GET", "data": { "x": 10, "y": 200 }})";

glz::json_t json{};
glz::read_json(json, buffer);
const &action = json["action"].get<std::string>();
if (action == "DELETE") {
    // Now we know the message type
    auto bomb = glz::read_json<bomb_t>(buffer);
}

@sweihub
Copy link
Author

sweihub commented Feb 1, 2023

Thanks for the thoughtful reply, BTW I never know there's a glz::json_t variant.

Since I am reluctant to integrate extra JSON libraries, So I will go with string pattern matching. Different JSON messages are so common in a websocket stream, so we have to decide the message types and deserialize them.

{"action":"subscribe", "symbols": ["BTCUSDT", "ETHUSDT"]}
{"action":"trade", "data": {"symbol": "ETHUSDT", "price": "123.4","volume":100}}

Substring matching

auto buffer = "{...}";
if (buffer.starts_with("{\"action\":\"subscribe\"")) {
    auto subscribe = glz::read_json<subscribe_t>(buffer);
}

Thanks anyway, I will stay tune to the project.

@sweihub sweihub closed this as completed Feb 1, 2023
@mwalcott3
Copy link
Collaborator

mwalcott3 commented Feb 1, 2023

Reopened as @stephenberry decided to add the feature originally requested.

@mwalcott3 mwalcott3 reopened this Feb 1, 2023
@stephenberry
Copy link
Owner

stephenberry commented Feb 1, 2023

@sweihub
#150 has been merged with main. This adds the ability to generally parse a targeted value.

Your example problem can now be solved as follows:

struct xy_t {
   int x{};
   int y{};
};

template <>
struct glz::meta<xy_t> {
   using T = xy_t;
   static constexpr auto value = object("x", &T::x, "y", &T::y);
};

struct bomb_t {
   xy_t data{};
};

template <>
struct glz::meta<bomb_t> {
   using T = bomb_t;
   static constexpr auto value = object("action", skip{}, "data", &T::data);
};

In use:

std::string buffer = R"( { "action": "DELETE", "data": { "x": 10, "y": 200 }})";
      
auto action = glz::get_sv_json<"/action">(buffer);

expect(action == R"("DELETE")");
if (action == R"("DELETE")") {
   auto bomb = glz::read_json<bomb_t>(buffer);
   expect(bomb.data.x == 10);
   expect(bomb.data.y == 200);
}

Notice that "action" is registered with a skip type, which tells the parser to just skip this value because you've already handled it. You could add action as a std::string to your bomb_t, and it would populate the action on parse, but this skip approach is just a bit faster if you've already handled the action.

glz::get_sv_json gets the targeted value as a std::string_view, so notice the extra quotes needed when checking the value. This is to get the best performance possible. If you wanted to check against "DELETE" as a string without quotes then you could call: auto action = glz::get_as_json<std::string, "/action">(buffer);
glz::get_as_json will actually parse into the desired type, which then would allow you to check the action as follows:

if (action == "DELETE")

I hope all this makes sense, feel free to ask questions. We'll be adding documentation for these functions in the future.

Repository owner locked and limited conversation to collaborators Feb 1, 2023
@stephenberry stephenberry converted this issue into discussion #151 Feb 1, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants