summaryrefslogtreecommitdiffstats
path: root/json.h
blob: f3d586bb402b13d42308867442371870022698e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/*
 * json.h
 *
 * Home page of code is: https://www.smartmontools.org
 *
 * Copyright (C) 2017-22 Christian Franke
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#ifndef JSON_H_CVSID
#define JSON_H_CVSID "$Id: json.h 5292 2022-01-06 17:13:25Z chrfranke $"

#include <stdint.h>
#include <stdio.h>
#include <initializer_list>
#include <map>
#include <memory>
#include <string>
#include <vector>

/// Create and print JSON output.
class json
{
public:
  /// Return true if value is a safe JSON integer.
  /// Same as Number.isSafeInteger(value) in JavaScript.
  static bool is_safe_uint(unsigned long long value)
    { return (value < (1ULL << 53)); }

  /// Replace space and non-alphanumerics with '_', upper to lower case.
  static std::string str2key(const char * str);

  /// Replace space and non-alphanumerics with '_', upper to lower case
  /// (std::string variant).
  static std::string str2key(const std::string & str)
    { return str2key(str.c_str()); }

  enum node_type {
    nt_unset, nt_object, nt_array,
    nt_bool, nt_int, nt_uint, nt_uint128, nt_string
  };

  // initializer_list<> elements.
  struct initlist_value {
    // cppcheck-suppress noExplicitConstructor
    initlist_value(node_type t) : type(t) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(bool v) : type(nt_bool), intval(v ? 1 : 0) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(int v) : initlist_value((long long)v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(unsigned v) : initlist_value((unsigned long long)v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(long v) : initlist_value((long long)v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(unsigned long v) : initlist_value((unsigned long long)v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(long long v) : type(nt_int), intval((uint64_t)(int64_t)v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(unsigned long long v) : type(nt_uint), intval((uint64_t)v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(const char * v) : type(nt_string), strval(v) {}
    // cppcheck-suppress noExplicitConstructor
    initlist_value(const std::string & v) : type(nt_string), strval(v.c_str()) {}
    node_type type;
    uint64_t intval = 0;
    const char * strval = nullptr;
  };

  struct initlist_key_value_pair {
    initlist_key_value_pair(const char * k, const initlist_value & v) : keystr(k), value(v) {}
    initlist_key_value_pair(const std::string & k, const initlist_value & v)
      : keystr(k.c_str()), value(v) {}
    initlist_key_value_pair(const char * k, const std::initializer_list<initlist_key_value_pair> & ilist)
      : keystr(k), value(nt_object), object(ilist) {}
    initlist_key_value_pair(const std::string & k, const std::initializer_list<initlist_key_value_pair> & ilist)
      : keystr(k.c_str()), value(nt_object), object(ilist) {}
    initlist_key_value_pair(const char * k, const std::initializer_list<initlist_value> & ilist)
      : keystr(k), value(nt_array), array(ilist) {}
    initlist_key_value_pair(const std::string & k, const std::initializer_list<initlist_value> & ilist)
      : keystr(k.c_str()), value(nt_array), array(ilist) {}
    const char * keystr;
    initlist_value value;
    std::initializer_list<initlist_key_value_pair> object;
    std::initializer_list<initlist_value> array;
  };

private:
  struct node_info
  {
    std::string key;
    int index = 0;

    node_info() = default;
    explicit node_info(const char * keystr) : key(str2key(keystr)) { }
    explicit node_info(int index_) : index(index_) { }
  };

  typedef std::vector<node_info> node_path;

public:
  /// Reference to a JSON element.
  class ref
  {
  public:
    ~ref();

    /// Return reference to object element.
    ref operator[](const char * keystr) const
      { return ref(*this, keystr); }

    /// Return reference to object element (std::string variant).
    ref operator[](const std::string & keystr) const
      { return ref(*this, keystr.c_str()); }

    /// Return reference to array element.
    ref operator[](int index) const
      { return ref(*this, index); }

    // Assignment operators create or change element.
    void operator=(bool value);

    void operator=(int value);
    void operator=(unsigned value);
    void operator=(long value);
    void operator=(unsigned long value);
    void operator=(long long value);
    void operator=(unsigned long long value);

    void operator=(const char * value);
    void operator=(const std::string & value);

    /// Return reference to element with KEY_SUFFIX appended to last key.
    ref with_suffix(const char * key_suffix) const
      { return ref(*this, "", key_suffix); }

    void set_uint128(uint64_t value_hi, uint64_t value_lo);

    // Output only if safe integer.
    bool set_if_safe_uint64(uint64_t value);
    bool set_if_safe_uint128(uint64_t value_hi, uint64_t value_lo);
    bool set_if_safe_le128(const void * pvalue);

    // If unsafe integer, output also as string with key "NUMBER_s".
    void set_unsafe_uint64(uint64_t value);
    void set_unsafe_uint128(uint64_t value_hi, uint64_t value_lo);
    void set_unsafe_le128(const void * pvalue);

    /// Braced-init-list support for nested objects.
    void operator+=(std::initializer_list<initlist_key_value_pair> ilist);
    /// Braced-init-list support for simple arrays.
    void operator+=(std::initializer_list<initlist_value> ilist);

  private:
    friend class json;
    explicit ref(json & js);
    ref(json & js, const char * keystr);
    ref(const ref & base, const char * keystr);
    ref(const ref & base, int index);
    ref(const ref & base, const char * /*dummy*/, const char * key_suffix);

    void operator=(const initlist_value & value)
      { m_js.set_initlist_value(m_path, value); }

    json & m_js;
    node_path m_path;
  };

  /// Return reference to element of top level object.
  ref operator[](const char * keystr)
    { return ref(*this, keystr); }

  /// Return reference to element of top level object (std::string variant).
  ref operator[](const std::string & keystr)
    { return ref(*this, keystr.c_str()); }

  /// Braced-init-list support for top level object.
  void operator+=(std::initializer_list<initlist_key_value_pair> ilist)
    { ref(*this) += ilist; }

  /// Enable/disable JSON output.
  void enable(bool yes = true)
    { m_enabled = yes; }

  /// Return true if enabled.
  bool is_enabled() const
    { return m_enabled; }

  /// Enable/disable extra string output for safe integers also.
  void set_verbose(bool yes = true)
    { m_verbose = yes; }

  /// Return true if any 128-bit value has been output.
  bool has_uint128_output() const
    { return m_uint128_output; }

  /// Options for print().
  struct print_options {
    bool pretty = false; //< Pretty-print output.
    bool sorted = false; //< Sort object keys.
    char format = 0; //< 'y': YAML, 'g': flat(grep, gron), other: JSON
  };

  /// Print JSON tree to a file.
  void print(FILE * f, const print_options & options) const;

private:
  struct node
  {
    node();
    node(const node &) = delete;
    explicit node(const std::string & key_);
    ~node();
    void operator=(const node &) = delete;

    node_type type = nt_unset;

    uint64_t intval = 0, intval_hi = 0;
    std::string strval;

    std::string key;
    std::vector< std::unique_ptr<node> > childs;
    typedef std::map<std::string, unsigned> keymap;
    keymap key2index;

    class const_iterator
    {
    public:
      const_iterator(const node * node_p, bool sorted);
      bool at_end() const;
      unsigned array_index() const;
      void operator++();
      const node * operator*() const;

    private:
      const node * m_node_p;
      bool m_use_map;
      unsigned m_child_idx = 0;
      keymap::const_iterator m_key_iter;
    };
  };

  bool m_enabled = false;
  bool m_verbose = false;
  bool m_uint128_output = false;

  node m_root_node;

  node * find_or_create_node(const node_path & path, node_type type);

  void set_bool(const node_path & path, bool value);
  void set_int64(const node_path & path, int64_t value);
  void set_uint64(const node_path & path, uint64_t value);
  void set_uint128(const node_path & path, uint64_t value_hi, uint64_t value_lo);
  void set_cstring(const node_path & path, const char * value);
  void set_string(const node_path & path, const std::string & value);
  void set_initlist_value(const node_path & path, const initlist_value & value);

  static void print_json(FILE * f, bool pretty, bool sorted, const node * p, int level);
  static void print_yaml(FILE * f, bool pretty, bool sorted, const node * p, int level_o,
                         int level_a, bool cont);
  static void print_flat(FILE * f, const char * assign, bool sorted, const node * p,
                         std::string & path);
};

#endif // JSON_H_CVSID