1 // Copyright 2024 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 #pragma once 15 16 /// @file pw_json/builder.h 17 /// 18 /// The pw_json module provides utilities for interacting with <a 19 /// href="http://www.json.org">JSON</a>. JSON is a structured data format that 20 /// supports strings, integers, floating point numbers, booleans, null, arrays, 21 /// and objects (key-value pairs). 22 /// 23 /// `pw::JsonBuilder` is a simple, efficient utility for serializing JSON to a 24 /// fixed-sized buffer. It works directly with the JSON wire format. It does not 25 /// support manipulation of previously serialized data. 26 /// 27 /// All `JsonBuilder` functions are `constexpr`, so may be used in `constexpr` 28 /// and `constinit` statements. 29 30 #include <cstddef> 31 #include <string_view> 32 #include <type_traits> 33 34 #include "pw_assert/assert.h" 35 #include "pw_json/internal/nesting.h" 36 #include "pw_span/span.h" 37 #include "pw_status/status.h" 38 #include "pw_status/status_with_size.h" 39 40 namespace pw { 41 42 /// @defgroup pw_json_builder_api 43 /// @{ 44 45 /// A `JsonArray` nested inside an object or array. Only provides functions for 46 /// appending values to the nested array. 47 /// 48 /// A `NestedJsonArray` is immediately invalidated if the enclosing JSON is 49 /// updated. Attempting to append to the nested array is an error. 50 class [[nodiscard]] NestedJsonArray { 51 public: 52 NestedJsonArray(const NestedJsonArray&) = delete; 53 NestedJsonArray& operator=(const NestedJsonArray&) = delete; 54 55 constexpr NestedJsonArray(NestedJsonArray&&) = default; 56 constexpr NestedJsonArray& operator=(NestedJsonArray&&) = default; 57 58 /// Appends to the nested array. 59 template <typename T> 60 constexpr NestedJsonArray& Append(const T& value); 61 62 /// Appends a new nested array to this nested array. 63 constexpr NestedJsonArray AppendNestedArray(); 64 65 /// Appends a new nested object to this nested array. 66 constexpr NestedJsonObject AppendNestedObject(); 67 68 private: 69 friend class JsonArray; 70 friend class JsonObject; 71 friend class NestedJsonObject; 72 NestedJsonArray(json_impl::NestedJson && nested)73 constexpr NestedJsonArray(json_impl::NestedJson&& nested) 74 : json_(std::move(nested)) {} 75 76 json_impl::NestedJson json_; 77 }; 78 79 /// A `JsonObject` nested inside an array or object. Only provides functions for 80 /// adding key-value pairs to the nested object. 81 /// 82 /// A `NestedJsonObject` is immediately invalidated if the enclosing JSON is 83 /// updated. Attempting to add to the nested object fails an assertion. 84 class [[nodiscard]] NestedJsonObject { 85 public: 86 NestedJsonObject(const NestedJsonObject&) = delete; 87 NestedJsonObject& operator=(const NestedJsonObject&) = delete; 88 89 constexpr NestedJsonObject(NestedJsonObject&&) = default; 90 constexpr NestedJsonObject& operator=(NestedJsonObject&&) = default; 91 92 /// Adds a key-value pair to the nested object. 93 template <typename T> 94 constexpr NestedJsonObject& Add(std::string_view key, const T& value); 95 96 /// Adds a nested array to the nested object. 97 constexpr NestedJsonArray AddNestedArray(std::string_view key); 98 99 /// Adds a nested object to the nested object. 100 constexpr NestedJsonObject AddNestedObject(std::string_view key); 101 102 private: 103 friend class JsonArray; 104 friend class JsonObject; 105 friend class NestedJsonArray; 106 NestedJsonObject(json_impl::NestedJson && nested)107 constexpr NestedJsonObject(json_impl::NestedJson&& nested) 108 : json_(std::move(nested)) {} 109 110 json_impl::NestedJson json_; 111 }; 112 113 /// Stores a simple JSON value: a string, integer, float, boolean, or null. 114 /// Provides a `Set()` function as well as the common functions for accessing 115 /// the serialized data (see documentation for `JsonBuilder`). 116 class JsonValue { 117 public: 118 constexpr JsonValue(const JsonValue&) = delete; 119 constexpr JsonValue& operator=(const JsonValue&) = delete; 120 121 // Functions common to all JSON types. 122 [[nodiscard]] constexpr bool IsValue() const; 123 [[nodiscard]] constexpr bool IsArray() const; 124 [[nodiscard]] constexpr bool IsObject() const; 125 126 constexpr operator std::string_view() const; 127 constexpr const char* data() const; 128 constexpr size_t size() const; 129 constexpr size_t max_size() const; 130 131 [[nodiscard]] constexpr bool ok() const; 132 constexpr Status status() const; 133 constexpr Status last_status() const; 134 constexpr void clear(); 135 constexpr void clear_status(); 136 137 /// Sets the JSON value to a boolean, number, string, or `null`. Sets and 138 /// returns the status. If a `Set` call fails, the value is set to `null`. 139 /// 140 /// It is an error to call `Set()` on a `JsonValue` if `StartArray` or 141 /// `StartObject` was called on the `JsonBuilder`. Setting the `JsonValue` to 142 /// a JSON object or array is also an error. 143 /// 144 /// @returns @rst 145 /// 146 /// .. pw-status-codes:: 147 /// 148 /// OK: The value serialized successfully. 149 /// 150 /// RESOURCE_EXHAUSTED: There is insufficient buffer space to 151 /// serialize. 152 /// 153 /// @endrst 154 template <typename T> 155 constexpr Status Set(const T& value); 156 157 private: 158 friend class JsonBuilder; 159 160 constexpr JsonValue() = default; 161 }; 162 163 /// Stores a JSON array: a sequence of values. Provides functions for adding 164 /// items to the array, as well as the common functions for accessing the 165 /// serialized data (see documentation for `JsonBuilder`). 166 class JsonArray { 167 public: 168 constexpr JsonArray(const JsonArray&) = delete; 169 constexpr JsonArray& operator=(const JsonArray&) = delete; 170 171 // Functions common to all JSON types. See documentation for `JsonBuilder`. 172 [[nodiscard]] constexpr bool IsValue() const; 173 [[nodiscard]] constexpr bool IsArray() const; 174 [[nodiscard]] constexpr bool IsObject() const; 175 176 constexpr operator std::string_view() const; 177 constexpr const char* data() const; 178 constexpr size_t size() const; 179 constexpr size_t max_size() const; 180 181 [[nodiscard]] constexpr bool ok() const; 182 constexpr Status status() const; 183 constexpr Status last_status() const; 184 constexpr void clear(); 185 constexpr void clear_status(); 186 187 /// Adds a value to the JSON array. Updates the status. 188 /// 189 /// It is an error to call `Append()` if the underlying `JsonBuilder` is no 190 /// longer an array. 191 template <typename T> 192 constexpr JsonArray& Append(const T& value); 193 194 /// Appends a nested array to this array. 195 constexpr NestedJsonArray AppendNestedArray(); 196 197 /// Appends a nested object to this array. 198 constexpr NestedJsonObject AppendNestedObject(); 199 200 /// Appends all elements from an iterable container. If there is an error, 201 /// changes are reverted. 202 template <typename Iterable> 203 constexpr JsonArray& Extend(const Iterable& iterable); 204 205 /// Appends all elements from an iterable container. If there is an error, 206 /// changes are reverted. 207 template <typename T, size_t kSize> 208 constexpr JsonArray& Extend(const T (&iterable)[kSize]); 209 210 private: 211 friend class JsonBuilder; 212 213 constexpr JsonArray() = default; 214 }; 215 216 /// Stores a JSON object: a sequence of key-value pairs. Provides functions 217 /// for adding entries to the object, as well as the common functions for 218 /// accessing the serialized data (see documentation for `JsonBuilder`). 219 class JsonObject { 220 public: 221 constexpr JsonObject(const JsonObject&) = delete; 222 constexpr JsonObject& operator=(const JsonObject&) = delete; 223 224 // Functions common to all JSON types. See documentation for `JsonBuilder`. 225 [[nodiscard]] constexpr bool IsValue() const; 226 [[nodiscard]] constexpr bool IsArray() const; 227 [[nodiscard]] constexpr bool IsObject() const; 228 229 constexpr operator std::string_view() const; 230 constexpr const char* data() const; 231 constexpr size_t size() const; 232 constexpr size_t max_size() const; 233 234 [[nodiscard]] constexpr bool ok() const; 235 constexpr Status status() const; 236 constexpr Status last_status() const; 237 constexpr void clear(); 238 constexpr void clear_status(); 239 240 /// Adds a key-value pair to the JSON object. Updates the status. 241 /// 242 /// It is an error to call `Add()` if the underlying `JsonBuilder` is no 243 /// longer an object. 244 /// 245 /// @returns @rst 246 /// 247 /// .. pw-status-codes:: 248 /// 249 /// OK: The value was appended successfully. 250 /// 251 /// RESOURCE_EXHAUSTED: Insufficient buffer space to serialize. 252 /// 253 /// @endrst 254 template <typename T> 255 constexpr JsonObject& Add(std::string_view key, const T& value); 256 257 template <typename T> 258 constexpr JsonObject& Add(std::nullptr_t, const T& value) = delete; 259 260 constexpr NestedJsonArray AddNestedArray(std::string_view key); 261 262 constexpr NestedJsonObject AddNestedObject(std::string_view key); 263 264 private: 265 friend class JsonBuilder; 266 267 constexpr JsonObject() = default; 268 }; 269 270 /// `JsonBuilder` is used to create arbitrary JSON. Contains a JSON value, which 271 /// may be an object or array. Arrays and objects may contain other values, 272 /// objects, or arrays. 273 class JsonBuilder : private JsonValue, private JsonArray, private JsonObject { 274 public: 275 /// `JsonBuilder` requires at least 5 characters in its buffer. MinBufferSize()276 static constexpr size_t MinBufferSize() { return 5; } 277 278 /// Initializes to the value `null`. `buffer.size()` must be at least 5. JsonBuilder(span<char> buffer)279 constexpr JsonBuilder(span<char> buffer) 280 : JsonBuilder(buffer.data(), buffer.size()) {} 281 282 /// Initializes to the value `null`. `buffer_size` must be at least 5. JsonBuilder(char * buffer,size_t buffer_size)283 constexpr JsonBuilder(char* buffer, size_t buffer_size) 284 : JsonBuilder(buffer, buffer_size, Uninitialized{}) { 285 PW_ASSERT(buffer_size >= MinBufferSize()); // Must be at least 5 characters 286 MakeNull(); 287 } 288 289 /// True if the top-level JSON entity is a simple value (not array or object). IsValue()290 [[nodiscard]] constexpr bool IsValue() const { 291 return !IsObject() && !IsArray(); 292 } 293 294 /// True if the top-level JSON entity is an array. IsArray()295 [[nodiscard]] constexpr bool IsArray() const { return buffer_[0] == '['; } 296 297 /// True if the top-level JSON entity is an object. IsObject()298 [[nodiscard]] constexpr bool IsObject() const { return buffer_[0] == '{'; } 299 300 /// `JsonBuilder` converts to `std::string_view`. string_view()301 constexpr operator std::string_view() const { return {data(), size()}; } 302 303 /// Pointer to the serialized JSON, which is always a null-terminated string. data()304 constexpr const char* data() const { return buffer_; } 305 306 /// The current size of the JSON string, excluding the null terminator. size()307 constexpr size_t size() const { return json_size_; } 308 309 /// The maximum size of the JSON string, excluding the null terminator. max_size()310 constexpr size_t max_size() const { return max_size_; } 311 312 /// True if @cpp_func{status} is @pw_status{OK}; no errors have occurred. ok()313 [[nodiscard]] constexpr bool ok() const { return status().ok(); } 314 315 /// Returns the `JsonBuilder`'s status, which reflects the first error that 316 /// occurred while updating the JSON. After an update fails, the non-`OK` 317 /// status remains until it is reset with `clear`, `clear_status`, or 318 /// `SetValue`. 319 /// 320 /// @returns @rst 321 /// 322 /// .. pw-status-codes:: 323 /// 324 /// OK: All previous updates have succeeded. 325 /// 326 /// RESOURCE_EXHAUSTED: An update did not fit in the buffer. 327 /// 328 /// @endrst status()329 constexpr Status status() const { return static_cast<Status::Code>(status_); } 330 331 /// Returns the status from the most recent change to the JSON. This is set 332 /// with each JSON update and may be @pw_status{OK} while `status()` is not. last_status()333 constexpr Status last_status() const { 334 return static_cast<Status::Code>(last_status_); 335 } 336 337 /// Sets the JSON `null` and clears the status. clear()338 constexpr void clear() { JsonValueClear(); } 339 340 /// Resets `status()` and `last_status()`. clear_status()341 constexpr void clear_status() { set_statuses(OkStatus()); } 342 343 /// Clears the JSON and sets it to a single JSON value (see `JsonValue::Set`). 344 template <typename T> 345 constexpr Status SetValue(const T& value); 346 347 /// Sets the JSON to `null` and returns a `JsonValue` reference to this 348 /// `JsonBuilder`. StartValue()349 [[nodiscard]] constexpr JsonValue& StartValue() { 350 JsonValueClear(); 351 return *this; 352 } 353 354 /// Clears the JSON and sets it to an empty array (`[]`). Returns a 355 /// `JsonArray` reference to this `JsonBuilder`. For example: 356 /// 357 /// @code{.cpp} 358 /// builder.StartArray() 359 /// .Append("item1") 360 /// .Append(2) 361 /// .Extend({"3", "4", "5"}); 362 /// @endcode StartArray()363 [[nodiscard]] constexpr JsonArray& StartArray() { 364 JsonArrayClear(); 365 return *this; 366 } 367 368 /// Clears the JSON and sets it to an empty object (`{}`). Returns a 369 /// `JsonObject` reference to this `JsonBuilder`. For example: 370 /// 371 /// @code{.cpp} 372 /// JsonBuffer<128> builder; 373 /// JsonObject& object = builder.StartObject() 374 /// .Add("key1", 1) 375 /// .Add("key2", "val2"); 376 /// object.Add("another", "entry"); 377 /// @endcode StartObject()378 [[nodiscard]] constexpr JsonObject& StartObject() { 379 JsonObjectClear(); 380 return *this; 381 } 382 383 protected: 384 enum class Uninitialized {}; 385 386 // Constructor that doesn't initialize the buffer. JsonBuilder(char * buffer,size_t buffer_size,Uninitialized)387 constexpr JsonBuilder(char* buffer, size_t buffer_size, Uninitialized) 388 : buffer_(buffer), 389 max_size_(buffer_size - 1), 390 json_size_(0), 391 status_(OkStatus().code()), 392 last_status_(OkStatus().code()) {} 393 394 // Sets the buffer to the null value. MakeNull()395 constexpr void MakeNull() { 396 buffer_[0] = 'n'; 397 buffer_[1] = 'u'; 398 buffer_[2] = 'l'; 399 buffer_[3] = 'l'; 400 buffer_[4] = '\0'; 401 json_size_ = 4; 402 } 403 set_json_size(size_t json_size)404 constexpr void set_json_size(size_t json_size) { json_size_ = json_size; } 405 set_statuses(Status status,Status last_status)406 constexpr void set_statuses(Status status, Status last_status) { 407 status_ = status.code(); 408 last_status_ = last_status.code(); 409 } 410 411 private: 412 friend class JsonValue; 413 friend class JsonArray; 414 friend class JsonObject; 415 416 friend class NestedJsonArray; 417 friend class NestedJsonObject; 418 remaining()419 constexpr size_t remaining() const { return max_size() - size(); } 420 421 // Sets last_status_ and updates status_ if an error occurred. 422 constexpr void update_status(Status new_status); 423 set_statuses(Status status)424 constexpr void set_statuses(Status status) { set_statuses(status, status); } 425 JsonValueClear()426 constexpr void JsonValueClear() { 427 MakeNull(); 428 set_statuses(OkStatus()); 429 } 430 JsonArrayClear()431 constexpr void JsonArrayClear() { 432 MakeEmpty('[', ']'); 433 set_statuses(OkStatus()); 434 } 435 JsonObjectClear()436 constexpr void JsonObjectClear() { 437 MakeEmpty('{', '}'); 438 set_statuses(OkStatus()); 439 } 440 441 template <typename T> 442 constexpr Status JsonValueSet(const T& value); 443 444 template <typename T> 445 constexpr JsonArray& JsonArrayAppend(const T& value); 446 447 template <typename Iterator> 448 constexpr JsonArray& JsonArrayExtend(Iterator begin, Iterator end); 449 450 template <typename T> 451 constexpr JsonObject& JsonObjectAdd(std::string_view key, const T& value); 452 453 [[nodiscard]] constexpr bool JsonArrayAddElement(); 454 455 // Adds the key if there's room for the key and at least one more character. 456 [[nodiscard]] constexpr bool JsonObjectAddKey(std::string_view key); 457 NestedJsonOffset(const json_impl::Nesting & nesting)458 constexpr size_t NestedJsonOffset(const json_impl::Nesting& nesting) const { 459 // Point to the start of the nested JSON array or object. This will be three 460 // characters, plus one for each prior layer of nesting {..., "": []}. 461 return json_size_ - 3 - nesting.depth(); 462 } 463 type()464 constexpr json_impl::Nesting::Type type() const { 465 return IsArray() ? json_impl::Nesting::kArray : json_impl::Nesting::kObject; 466 } 467 468 constexpr json_impl::NestedJson JsonArrayAppendNested( 469 const char (&open_close)[2], const json_impl::Nesting& nesting); 470 471 constexpr json_impl::NestedJson JsonObjectAddNested( 472 std::string_view key, 473 const char (&open_close)[2], 474 const json_impl::Nesting& nesting); 475 476 // Nesting works by shrinking the JsonBuilder to be just the nested structure, 477 // then expanding back out when done adding items. 478 constexpr void AddNestedStart(const json_impl::Nesting& nesting); 479 constexpr void AddNestedFinish(const json_impl::Nesting& nesting); 480 481 template <typename T> 482 constexpr void NestedJsonArrayAppend(const T& value, 483 const json_impl::Nesting& nesting); 484 485 template <typename T> 486 constexpr void NestedJsonObjectAdd(std::string_view key, 487 const T& value, 488 const json_impl::Nesting& nesting); 489 490 // For a single JSON value, checks if writing succeeded and clears on failure. 491 constexpr Status HandleSet(StatusWithSize written); 492 493 // For a value added to an array or object, checks if writing the characters 494 // succeeds, sets the status, and terminates the buffer as appropriate. 495 constexpr void HandleAdd(StatusWithSize written, 496 size_t starting_size, 497 char terminator); 498 MakeEmpty(char open,char close)499 constexpr void MakeEmpty(char open, char close) { 500 buffer_[0] = open; 501 buffer_[1] = close; 502 buffer_[2] = '\0'; 503 json_size_ = 2; 504 } 505 506 // TODO: b/326097937 - Use StringBuilder here. 507 char* buffer_; 508 size_t max_size_; // max size of the JSON string, excluding the '\0' 509 size_t json_size_; 510 511 // If any errors have occurred, status_ stores the most recent error Status. 512 // last_status_ stores the status from the most recent operation. 513 uint8_t status_; 514 uint8_t last_status_; 515 }; 516 517 /// A `JsonBuilder` with an integrated buffer. The buffer will be sized to fit 518 /// `kMaxSize` characters. 519 template <size_t kMaxSize> 520 class JsonBuffer final : public JsonBuilder { 521 public: 522 // Constructs a JsonBuffer with the value null. JsonBuffer()523 constexpr JsonBuffer() 524 : JsonBuilder(static_buffer_, sizeof(static_buffer_), Uninitialized{}), 525 static_buffer_{} { 526 MakeNull(); 527 } 528 529 template <typename T> Value(const T & initial_value)530 static constexpr JsonBuffer Value(const T& initial_value) { 531 JsonBuffer<kMaxSize> json; 532 PW_ASSERT(json.SetValue(initial_value).ok()); // Failed serialization. 533 return json; 534 } 535 536 // JsonBuffers may be copied or assigned, as long as the source buffer is not 537 // larger than this buffer. JsonBuffer(const JsonBuffer & other)538 constexpr JsonBuffer(const JsonBuffer& other) : JsonBuffer() { 539 CopyFrom(other); 540 } 541 542 template <size_t kOtherSize> JsonBuffer(const JsonBuffer<kOtherSize> & other)543 constexpr JsonBuffer(const JsonBuffer<kOtherSize>& other) : JsonBuffer() { 544 CopyFrom(other); 545 } 546 547 constexpr JsonBuffer& operator=(const JsonBuffer& rhs) { 548 CopyFrom(rhs); 549 return *this; 550 } 551 552 template <size_t kOtherSize> 553 constexpr JsonBuffer& operator=(const JsonBuffer<kOtherSize>& rhs) { 554 CopyFrom(rhs); 555 return *this; 556 } 557 max_size()558 static constexpr size_t max_size() { return kMaxSize; } 559 560 private: 561 static_assert(kMaxSize + 1 /* null */ >= JsonBuilder::MinBufferSize(), 562 "JsonBuffers require at least 4 bytes"); 563 564 template <size_t kOtherSize> CopyFrom(const JsonBuffer<kOtherSize> & other)565 constexpr void CopyFrom(const JsonBuffer<kOtherSize>& other) { 566 static_assert(kOtherSize <= kMaxSize, 567 "A JsonBuffer cannot be copied into a smaller buffer"); 568 CopyFrom(static_cast<const JsonBuilder&>(other)); 569 } 570 CopyFrom(const JsonBuilder & other)571 constexpr void CopyFrom(const JsonBuilder& other) { 572 for (size_t i = 0; i < other.size() + 1 /* include null */; ++i) { 573 static_buffer_[i] = other.data()[i]; 574 } 575 JsonBuilder::set_json_size(other.size()); 576 JsonBuilder::set_statuses(other.status(), other.last_status()); 577 } 578 579 char static_buffer_[kMaxSize + 1]; 580 }; 581 582 /// @} 583 584 } // namespace pw 585 586 // Functions are defined inline in a separate header. 587 #include "pw_json/internal/builder_impl.h" 588