HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
JSON.hpp
1// Copyright Take Vos 2019.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../file/file.hpp"
8#include "../parser/parser.hpp"
9#include "../utility/utility.hpp"
10#include "../algorithm/algorithm.hpp"
11#include "datum.hpp"
12#include "indent.hpp"
13#include "../macros.hpp"
14#include <string>
15#include <string_view>
16#include <vector>
17#include <optional>
18
19hi_export_module(hikogui.codec.JSON);
20
21hi_export namespace hi::inline v1 {
22namespace detail {
23
24template<std::input_iterator It, std::sentinel_for<It> ItEnd>
25[[nodiscard]] constexpr std::optional<datum> json_parse_value(It &it, ItEnd last, std::string_view path);
26
27template<std::input_iterator It, std::sentinel_for<It> ItEnd>
28[[nodiscard]] constexpr std::optional<datum> json_parse_array(It &it, ItEnd last, std::string_view path)
29{
30 auto r = datum::make_vector();
31
32 // Required '['
33 if (*it == '[') {
34 ++it;
35 } else {
36 return std::nullopt;
37 }
38
39 auto comma_after_value = true;
40 while (true) {
41 // A ']' is required at end of configuration-items.
42 if (*it == ']') {
43 ++it;
44 break;
45
46 // Required a value.
47 } else if (auto result = json_parse_value(it, last, path)) {
48 if (not comma_after_value) {
49 throw parse_error(std::format("{}: Expecting ',', found {}", token_location(it, last, path), *it));
50 }
51
52 r.push_back(std::move(*result));
53
54 if (*it == ',') {
55 ++it;
56 comma_after_value = true;
57 } else {
58 comma_after_value = false;
59 }
60
61 } else {
62 throw parse_error(std::format("{}: Expecting a JSON value, found {}", token_location(it, last, path), *it));
63 }
64 }
65
66 return r;
67}
68
69template<std::input_iterator It, std::sentinel_for<It> ItEnd>
70[[nodiscard]] constexpr std::optional<datum> json_parse_object(It& it, ItEnd last, std::string_view path)
71{
72 auto r = datum::make_map();
73
74 // Required '{'
75 if (*it == '{') {
76 ++it;
77
78 } else {
79 return std::nullopt;
80 }
81
82 auto comma_after_value = true;
83 while (true) {
84 // A '}' is required at end of configuration-items.
85 if (*it == '}') {
86 ++it;
87 break;
88
89 // Required a string name.
90 } else if (*it == token::dstr) {
91 if (not comma_after_value) {
92 throw parse_error(std::format("{}: Expecting ',', found {}.", token_location(it, last, path), *it));
93 }
94
95 auto name = static_cast<std::string>(*it++);
96
97 if ((*it == ':')) {
98 ++it;
99 } else {
100 throw parse_error(std::format("{}: Expecting ':', found {}.", token_location(it, last, path), *it));
101 }
102
103 if (auto result = json_parse_value(it, last, path)) {
104 r[name] = std::move(*result);
105
106 } else {
107 throw parse_error(
108 std::format("{}: Expecting a JSON value, found {}.", token_location(it, last, path), *it));
109 }
110
111 if (*it == ',') {
112 ++it;
113 comma_after_value = true;
114 } else {
115 comma_after_value = false;
116 }
117
118 } else {
119 throw parse_error(std::format(
120 "{}: Unexpected token {}, expected a key or close-brace.", token_location(it, last, path), *it));
121 }
122 }
123
124 return r;
125}
126
127template<std::input_iterator It, std::sentinel_for<It> ItEnd>
128[[nodiscard]] constexpr std::optional<datum> json_parse_value(It& it, ItEnd last, std::string_view path)
129{
130 hi_assert(it != last);
131
132 if (*it == token::dstr) {
133 return datum{static_cast<std::string>(*it++)};
134
135 } else if (*it == token::integer) {
136 return datum{static_cast<long long>(*it++)};
137
138 } else if (*it == token::real) {
139 return datum{static_cast<double>(*it++)};
140
141 } else if (*it == '-') {
142 ++it;
143 if (*it == token::integer) {
144 return datum{-static_cast<long long>(*it++)};
145
146 } else if (*it == token::real) {
147 return datum{-static_cast<double>(*it++)};
148
149 } else {
150 throw parse_error(std::format(
151 "{}: Unexpected token '{}' after '-', expected integer or floating point literal.",
152 token_location(it, last, path),
153 *it));
154 }
155
156 } else if (*it == token::id and *it == "true") {
157 ++it;
158 return datum{true};
159
160 } else if (*it == token::id and *it == "false") {
161 ++it;
162 return datum{false};
163
164 } else if (*it == token::id and *it == "null") {
165 ++it;
166 return datum{nullptr};
167
168 } else if (auto object = json_parse_object(it, last, path)) {
169 return *object;
170
171 } else if (auto array = json_parse_array(it, last, path)) {
172 return *array;
173
174 } else {
175 return std::nullopt;
176 }
177}
178
179} // namespace detail
180
181hi_export template<std::input_iterator It, std::sentinel_for<It> ItEnd>
182[[nodiscard]] constexpr datum parse_JSON(It it, ItEnd last, std::string_view path = std::string_view{"<none>"})
183{
184 auto token_it = lexer<lexer_config::json_style()>.parse(it, last);
185
186 if (token_it == std::default_sentinel) {
187 throw parse_error(std::format("{}: No tokens found", token_location(token_it, std::default_sentinel, path)));
188 }
189
190 auto r = datum{};
191 if (auto result = json_parse_value(token_it, std::default_sentinel, path)) {
192 r = std::move(*result);
193
194 } else {
195 throw parse_error(std::format("{}: Missing JSON object", token_location(token_it, std::default_sentinel, path)));
196 }
197
198 if (token_it != std::default_sentinel) {
199 throw parse_error(
200 std::format("{}: Unexpected text after JSON root object", token_location(token_it, std::default_sentinel, path)));
201 }
202
203 return r;
204}
205
210hi_export [[nodiscard]] constexpr datum parse_JSON(std::string_view text, std::string_view path = std::string_view{"<none>"})
211{
212 return parse_JSON(text.cbegin(), text.cend(), path);
213}
214
219hi_export [[nodiscard]] constexpr datum parse_JSON(std::string const& text, std::string_view path = std::string_view{"<none>"})
220{
221 return parse_JSON(std::string_view{text}, path);
222}
223
228hi_export [[nodiscard]] constexpr datum parse_JSON(char const *text, std::string_view path = std::string_view{"<none>"})
229{
230 return parse_JSON(std::string_view{text}, path);
231}
232
237hi_export [[nodiscard]] inline datum parse_JSON(std::filesystem::path const& path)
238{
239 return parse_JSON(as_string_view(file_view(path)), path.string());
240}
241
242hi_export constexpr void format_JSON_impl(datum const& value, std::string& result, hi::indent indent = {})
243{
244 if (holds_alternative<nullptr_t>(value)) {
245 result += "null";
246 } else if (auto const *b = get_if<bool>(value)) {
247 result += *b ? "true" : "false";
248 } else if (auto const *i = get_if<long long>(value)) {
249 result += hi::to_string(*i);
250 } else if (auto const *f = get_if<double>(value)) {
251 result += hi::to_string(*f);
252 } else if (auto const *s = get_if<std::string>(value)) {
253 result += '"';
254 for (auto const c : *s) {
255 switch (c) {
256 case '\n':
257 result += '\\';
258 result += 'n';
259 break;
260 case '\r':
261 result += '\\';
262 result += 'r';
263 break;
264 case '\t':
265 result += '\\';
266 result += 't';
267 break;
268 case '\f':
269 result += '\\';
270 result += 'f';
271 break;
272 case '"':
273 result += '\\';
274 result += '"';
275 break;
276 default:
277 result += c;
278 }
279 }
280 result += '"';
281
282 } else if (auto const *v = get_if<datum::vector_type>(value)) {
283 result += indent;
284 result += '[';
285 result += '\n';
286
287 for (auto it = v->begin(); it != v->end(); it++) {
288 if (it != v->begin()) {
289 result += ',';
290 result += '\n';
291 }
292 result += indent + 1;
293
294 format_JSON_impl(*it, result, indent + 1);
295 }
296
297 result += '\n';
298 result += indent;
299 result += ']';
300 } else if (auto const *m = get_if<datum::map_type>(value)) {
301 result += indent;
302 result += '{';
303 result += '\n';
304
305 for (auto it = m->begin(); it != m->end(); it++) {
306 if (it != m->begin()) {
307 result += ',';
308 result += '\n';
309 }
310 result += indent + 1;
311
312 format_JSON_impl(it->first, result, indent + 1);
313 result += ':';
314 result += ' ';
315 format_JSON_impl(it->second, result, indent + 1);
316 }
317
318 result += '\n';
319 result += indent;
320 result += '}';
321 } else {
322 hi_no_default();
323 }
324}
325
330hi_export [[nodiscard]] constexpr std::string format_JSON(datum const& root)
331{
332 auto r = std::string{};
333 format_JSON_impl(root, r);
334 r += '\n';
335 return r;
336}
337
338} // namespace hi::inline v1
Defines the file class.
hi_export constexpr std::string token_location(It &it, ItEnd last, std::string_view path) noexcept
Create a location string for error messages.
Definition token.hpp:163
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
hi_export constexpr std::string format_JSON(datum const &root)
Dump an datum object into a JSON string.
Definition JSON.hpp:330
Indentation for writing out text files.
Definition indent.hpp:20
T move(T... args)