1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef UTIL_JSON_JSON_HELPERS_H_
6 #define UTIL_JSON_JSON_HELPERS_H_
7
8 #include <chrono>
9 #include <cmath>
10 #include <functional>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "absl/strings/string_view.h"
16 #include "json/value.h"
17 #include "platform/base/error.h"
18 #include "util/chrono_helpers.h"
19 #include "util/simple_fraction.h"
20
21 // This file contains helper methods for parsing JSON, in an attempt to
22 // reduce boilerplate code when working with JsonCpp.
23 namespace openscreen {
24 namespace json {
25
26 // TODO(jophba): remove these methods after refactoring offer messaging.
CreateParseError(const std::string & type)27 inline Error CreateParseError(const std::string& type) {
28 return Error(Error::Code::kJsonParseError, "Failed to parse " + type);
29 }
30
CreateParameterError(const std::string & type)31 inline Error CreateParameterError(const std::string& type) {
32 return Error(Error::Code::kParameterInvalid, "Invalid parameter: " + type);
33 }
34
ParseBool(const Json::Value & parent,const std::string & field)35 inline ErrorOr<bool> ParseBool(const Json::Value& parent,
36 const std::string& field) {
37 const Json::Value& value = parent[field];
38 if (!value.isBool()) {
39 return CreateParseError("bool field " + field);
40 }
41 return value.asBool();
42 }
43
ParseInt(const Json::Value & parent,const std::string & field)44 inline ErrorOr<int> ParseInt(const Json::Value& parent,
45 const std::string& field) {
46 const Json::Value& value = parent[field];
47 if (!value.isInt()) {
48 return CreateParseError("integer field: " + field);
49 }
50 return value.asInt();
51 }
52
ParseUint(const Json::Value & parent,const std::string & field)53 inline ErrorOr<uint32_t> ParseUint(const Json::Value& parent,
54 const std::string& field) {
55 const Json::Value& value = parent[field];
56 if (!value.isUInt()) {
57 return CreateParseError("unsigned integer field: " + field);
58 }
59 return value.asUInt();
60 }
61
ParseString(const Json::Value & parent,const std::string & field)62 inline ErrorOr<std::string> ParseString(const Json::Value& parent,
63 const std::string& field) {
64 const Json::Value& value = parent[field];
65 if (!value.isString()) {
66 return CreateParseError("string field: " + field);
67 }
68 return value.asString();
69 }
70
71 // TODO(jophba): offer messaging should use these methods instead.
ParseBool(const Json::Value & value,bool * out)72 inline bool ParseBool(const Json::Value& value, bool* out) {
73 if (!value.isBool()) {
74 return false;
75 }
76 *out = value.asBool();
77 return true;
78 }
79
80 // A general note about parsing primitives. "Validation" in this context
81 // generally means ensuring that the values are non-negative, excepting doubles
82 // which may be negative in some cases.
83 inline bool ParseAndValidateDouble(const Json::Value& value,
84 double* out,
85 bool allow_negative = false) {
86 if (!value.isDouble()) {
87 return false;
88 }
89 const double d = value.asDouble();
90 if (std::isnan(d)) {
91 return false;
92 }
93 if (!allow_negative && d < 0) {
94 return false;
95 }
96 *out = d;
97 return true;
98 }
99
ParseAndValidateInt(const Json::Value & value,int * out)100 inline bool ParseAndValidateInt(const Json::Value& value, int* out) {
101 if (!value.isInt()) {
102 return false;
103 }
104 int i = value.asInt();
105 if (i < 0) {
106 return false;
107 }
108 *out = i;
109 return true;
110 }
111
ParseAndValidateUint(const Json::Value & value,uint32_t * out)112 inline bool ParseAndValidateUint(const Json::Value& value, uint32_t* out) {
113 if (!value.isUInt()) {
114 return false;
115 }
116 *out = value.asUInt();
117 return true;
118 }
119
ParseAndValidateString(const Json::Value & value,std::string * out)120 inline bool ParseAndValidateString(const Json::Value& value, std::string* out) {
121 if (!value.isString()) {
122 return false;
123 }
124 *out = value.asString();
125 return true;
126 }
127
128 // We want to be more robust when we parse fractions then just
129 // allowing strings, this will parse numeral values such as
130 // value: 50 as well as value: "50" and value: "100/2".
ParseAndValidateSimpleFraction(const Json::Value & value,SimpleFraction * out)131 inline bool ParseAndValidateSimpleFraction(const Json::Value& value,
132 SimpleFraction* out) {
133 if (value.isInt()) {
134 int parsed = value.asInt();
135 if (parsed < 0) {
136 return false;
137 }
138 *out = SimpleFraction{parsed, 1};
139 return true;
140 }
141
142 if (value.isString()) {
143 auto fraction_or_error = SimpleFraction::FromString(value.asString());
144 if (!fraction_or_error) {
145 return false;
146 }
147
148 if (!fraction_or_error.value().is_positive() ||
149 !fraction_or_error.value().is_defined()) {
150 return false;
151 }
152 *out = std::move(fraction_or_error.value());
153 return true;
154 }
155 return false;
156 }
157
ParseAndValidateMilliseconds(const Json::Value & value,milliseconds * out)158 inline bool ParseAndValidateMilliseconds(const Json::Value& value,
159 milliseconds* out) {
160 int out_ms;
161 if (!ParseAndValidateInt(value, &out_ms) || out_ms < 0) {
162 return false;
163 }
164 *out = milliseconds(out_ms);
165 return true;
166 }
167
168 template <typename T>
169 using Parser = std::function<bool(const Json::Value&, T*)>;
170
171 // NOTE: array parsing methods reset the output vector to an empty vector in
172 // any error case. This is especially useful for optional arrays.
173 template <typename T>
ParseAndValidateArray(const Json::Value & value,Parser<T> parser,std::vector<T> * out)174 bool ParseAndValidateArray(const Json::Value& value,
175 Parser<T> parser,
176 std::vector<T>* out) {
177 out->clear();
178 if (!value.isArray() || value.empty()) {
179 return false;
180 }
181
182 out->reserve(value.size());
183 for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
184 T v;
185 if (!parser(value[i], &v)) {
186 out->clear();
187 return false;
188 }
189 out->push_back(v);
190 }
191
192 return true;
193 }
194
ParseAndValidateIntArray(const Json::Value & value,std::vector<int> * out)195 inline bool ParseAndValidateIntArray(const Json::Value& value,
196 std::vector<int>* out) {
197 return ParseAndValidateArray<int>(value, ParseAndValidateInt, out);
198 }
199
ParseAndValidateUintArray(const Json::Value & value,std::vector<uint32_t> * out)200 inline bool ParseAndValidateUintArray(const Json::Value& value,
201 std::vector<uint32_t>* out) {
202 return ParseAndValidateArray<uint32_t>(value, ParseAndValidateUint, out);
203 }
204
ParseAndValidateStringArray(const Json::Value & value,std::vector<std::string> * out)205 inline bool ParseAndValidateStringArray(const Json::Value& value,
206 std::vector<std::string>* out) {
207 return ParseAndValidateArray<std::string>(value, ParseAndValidateString, out);
208 }
209
210 } // namespace json
211 } // namespace openscreen
212
213 #endif // UTIL_JSON_JSON_HELPERS_H_
214