xref: /aosp_15_r20/external/executorch/backends/apple/coreml/runtime/util/json_util.cpp (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1 //
2 //  json_util.cpp
3 //  util
4 //
5 // Copyright © 2024 Apple Inc. All rights reserved.
6 //
7 // Please refer to the license found in the LICENSE file in the root directory of the source tree.
8 
9 #include "json_util.hpp"
10 
11 #include <string>
12 #include <vector>
13 
14 namespace {
15 
16 struct JSONParseState {
17     int n_open_braces = 0;
18     std::string json_object;
19     bool reading_string = false;
20 };
21 
is_quote_escaped(const std::vector<char> & buffer,size_t offset)22 bool is_quote_escaped(const std::vector<char>& buffer, size_t offset) {
23     return offset >= 1 && buffer[offset - 1] == '\\';
24 }
25 
process_quotes(const std::vector<char> & buffer,JSONParseState & state,size_t offset)26 void process_quotes(const std::vector<char>& buffer, JSONParseState& state, size_t offset) {
27     // If we are reading a string and the quote is escaped then we don't mark it as the end of string.
28     if (state.reading_string && is_quote_escaped(buffer, offset)) {
29         return;
30     }
31     state.reading_string = !state.reading_string;
32 }
33 
34 // Foundation has no streaming parser, so we try to read the top json object.
35 // At this stage we don't care about the validity of json, the parser would
36 // anyways fail if the json object is not valid.
read_object_from_buffer(std::vector<char> & buffer,JSONParseState & state)37 void read_object_from_buffer(std::vector<char>& buffer, JSONParseState& state) {
38     size_t offset = 0;
39     for (; offset < buffer.size() && state.n_open_braces > 0; offset++) {
40         switch (buffer[offset]) {
41             case '}': {
42                 // If the close brace is inside a string then then ignore it.
43                 state.n_open_braces -= (state.reading_string ? 0 : 1);
44                 break;
45             }
46             case '{': {
47                 // If the open brace is inside a string then ignore it.
48                 state.n_open_braces += (state.reading_string ? 0 : 1);
49                 break;
50             }
51             case '"': {
52                 process_quotes(buffer, state, offset);
53                 break;
54             }
55             default: {
56                 break;
57             }
58         }
59     }
60 
61     if (offset > 0) {
62         state.json_object.append(buffer.begin(), buffer.begin() + offset);
63     }
64 }
65 }
66 
67 namespace executorchcoreml {
68 namespace json {
69 
read_object_from_stream(std::istream & stream,size_t max_bytes_to_read)70 std::optional<std::string> read_object_from_stream(std::istream& stream, size_t max_bytes_to_read) {
71     static constexpr size_t buffer_size = 512;
72     JSONParseState state;
73     char ch;
74     // Ignore 0, 0 is added for padding.
75     do {
76         stream >> ch;
77     } while (stream.good() && static_cast<uint8_t>(ch) == 0);
78     // The first character must be an opening brace.
79     if (ch != '{') {
80         return std::optional<std::string>();
81     }
82     state.json_object += ch;
83     state.n_open_braces = 1;
84 
85     std::vector<char> buffer;
86     while (stream.good() && state.n_open_braces > 0 && state.json_object.size() < max_bytes_to_read) {
87         buffer.resize(buffer_size, '\0');
88         stream.read(buffer.data(), buffer_size);
89         read_object_from_buffer(buffer, state);
90         buffer.clear();
91     }
92 
93     return state.n_open_braces == 0 ? state.json_object : std::optional<std::string>();
94 }
95 
96 } // namespace json
97 } // namespace executorchcoreml
98