xref: /aosp_15_r20/external/pigweed/pw_json/public/pw_json/builder.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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