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/json/json_serialization.h"
20 #include "util/simple_fraction.h"
21
22 // This file contains helper methods for parsing JSON, in an attempt to
23 // reduce boilerplate code when working with JsonCpp.
24 namespace openscreen {
25 namespace json {
26
TryParseBool(const Json::Value & value,bool * out)27 inline bool TryParseBool(const Json::Value& value, bool* out) {
28 if (!value.isBool()) {
29 return false;
30 }
31 *out = value.asBool();
32 return true;
33 }
34
35 // A general note about parsing primitives. "Validation" in this context
36 // generally means ensuring that the values are non-negative, excepting doubles
37 // which may be negative in some cases.
38 inline bool TryParseDouble(const Json::Value& value,
39 double* out,
40 bool allow_negative = false) {
41 if (!value.isDouble()) {
42 return false;
43 }
44 const double d = value.asDouble();
45 if (std::isnan(d)) {
46 return false;
47 }
48 if (!allow_negative && d < 0) {
49 return false;
50 }
51 *out = d;
52 return true;
53 }
54
TryParseInt(const Json::Value & value,int * out)55 inline bool TryParseInt(const Json::Value& value, int* out) {
56 if (!value.isInt()) {
57 return false;
58 }
59 int i = value.asInt();
60 if (i < 0) {
61 return false;
62 }
63 *out = i;
64 return true;
65 }
66
TryParseUint(const Json::Value & value,uint32_t * out)67 inline bool TryParseUint(const Json::Value& value, uint32_t* out) {
68 if (!value.isUInt()) {
69 return false;
70 }
71 *out = value.asUInt();
72 return true;
73 }
74
TryParseString(const Json::Value & value,std::string * out)75 inline bool TryParseString(const Json::Value& value, std::string* out) {
76 if (!value.isString()) {
77 return false;
78 }
79 *out = value.asString();
80 return true;
81 }
82
83 // We want to be more robust when we parse fractions then just
84 // allowing strings, this will parse numeral values such as
85 // value: 50 as well as value: "50" and value: "100/2".
TryParseSimpleFraction(const Json::Value & value,SimpleFraction * out)86 inline bool TryParseSimpleFraction(const Json::Value& value,
87 SimpleFraction* out) {
88 if (value.isInt()) {
89 int parsed = value.asInt();
90 if (parsed < 0) {
91 return false;
92 }
93 *out = SimpleFraction{parsed, 1};
94 return true;
95 }
96
97 if (value.isString()) {
98 auto fraction_or_error = SimpleFraction::FromString(value.asString());
99 if (!fraction_or_error) {
100 return false;
101 }
102
103 if (!fraction_or_error.value().is_positive() ||
104 !fraction_or_error.value().is_defined()) {
105 return false;
106 }
107 *out = std::move(fraction_or_error.value());
108 return true;
109 }
110 return false;
111 }
112
TryParseMilliseconds(const Json::Value & value,milliseconds * out)113 inline bool TryParseMilliseconds(const Json::Value& value, milliseconds* out) {
114 int out_ms;
115 if (!TryParseInt(value, &out_ms) || out_ms < 0) {
116 return false;
117 }
118 *out = milliseconds(out_ms);
119 return true;
120 }
121
122 template <typename T>
123 using Parser = std::function<bool(const Json::Value&, T*)>;
124
125 // NOTE: array parsing methods reset the output vector to an empty vector in
126 // any error case. This is especially useful for optional arrays.
127 template <typename T>
TryParseArray(const Json::Value & value,Parser<T> parser,std::vector<T> * out)128 bool TryParseArray(const Json::Value& value,
129 Parser<T> parser,
130 std::vector<T>* out) {
131 out->clear();
132 if (!value.isArray() || value.empty()) {
133 return false;
134 }
135
136 out->reserve(value.size());
137 for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
138 T v;
139 if (!parser(value[i], &v)) {
140 out->clear();
141 return false;
142 }
143 out->push_back(v);
144 }
145
146 return true;
147 }
148
TryParseIntArray(const Json::Value & value,std::vector<int> * out)149 inline bool TryParseIntArray(const Json::Value& value, std::vector<int>* out) {
150 return TryParseArray<int>(value, TryParseInt, out);
151 }
152
TryParseUintArray(const Json::Value & value,std::vector<uint32_t> * out)153 inline bool TryParseUintArray(const Json::Value& value,
154 std::vector<uint32_t>* out) {
155 return TryParseArray<uint32_t>(value, TryParseUint, out);
156 }
157
TryParseStringArray(const Json::Value & value,std::vector<std::string> * out)158 inline bool TryParseStringArray(const Json::Value& value,
159 std::vector<std::string>* out) {
160 return TryParseArray<std::string>(value, TryParseString, out);
161 }
162
163 } // namespace json
164 } // namespace openscreen
165
166 #endif // UTIL_JSON_JSON_HELPERS_H_
167