diff options
Diffstat (limited to 'util/json/json_helpers.h')
-rw-r--r-- | util/json/json_helpers.h | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/util/json/json_helpers.h b/util/json/json_helpers.h new file mode 100644 index 00000000..a4c43479 --- /dev/null +++ b/util/json/json_helpers.h @@ -0,0 +1,209 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UTIL_JSON_JSON_HELPERS_H_ +#define UTIL_JSON_JSON_HELPERS_H_ + +#include <chrono> +#include <functional> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "json/value.h" +#include "platform/base/error.h" +#include "util/chrono_helpers.h" +#include "util/simple_fraction.h" + +// This file contains helper methods for parsing JSON, in an attempt to +// reduce boilerplate code when working with JsonCpp. +namespace openscreen { +namespace json { + +// TODO(jophba): remove these methods after refactoring offer messaging. +inline Error CreateParseError(const std::string& type) { + return Error(Error::Code::kJsonParseError, "Failed to parse " + type); +} + +inline Error CreateParameterError(const std::string& type) { + return Error(Error::Code::kParameterInvalid, "Invalid parameter: " + type); +} + +inline ErrorOr<bool> ParseBool(const Json::Value& parent, + const std::string& field) { + const Json::Value& value = parent[field]; + if (!value.isBool()) { + return CreateParseError("bool field " + field); + } + return value.asBool(); +} + +inline ErrorOr<int> ParseInt(const Json::Value& parent, + const std::string& field) { + const Json::Value& value = parent[field]; + if (!value.isInt()) { + return CreateParseError("integer field: " + field); + } + return value.asInt(); +} + +inline ErrorOr<uint32_t> ParseUint(const Json::Value& parent, + const std::string& field) { + const Json::Value& value = parent[field]; + if (!value.isUInt()) { + return CreateParseError("unsigned integer field: " + field); + } + return value.asUInt(); +} + +inline ErrorOr<std::string> ParseString(const Json::Value& parent, + const std::string& field) { + const Json::Value& value = parent[field]; + if (!value.isString()) { + return CreateParseError("string field: " + field); + } + return value.asString(); +} + +// TODO(jophba): offer messaging should use these methods instead. +inline bool ParseBool(const Json::Value& value, bool* out) { + if (!value.isBool()) { + return false; + } + *out = value.asBool(); + return true; +} + +// A general note about parsing primitives. "Validation" in this context +// generally means ensuring that the values are non-negative. There are +// currently no cases in our usage of JSON strings where we accept negative +// values. If this changes in the future, care must be taken to ensure +// that we don't break anything in existing code. +inline bool ParseAndValidateDouble(const Json::Value& value, double* out) { + if (!value.isDouble()) { + return false; + } + const double d = value.asDouble(); + if (d < 0) { + return false; + } + *out = d; + return true; +} + +inline bool ParseAndValidateInt(const Json::Value& value, int* out) { + if (!value.isInt()) { + return false; + } + int i = value.asInt(); + if (i < 0) { + return false; + } + *out = i; + return true; +} + +inline bool ParseAndValidateUint(const Json::Value& value, uint32_t* out) { + if (!value.isUInt()) { + return false; + } + *out = value.asUInt(); + return true; +} + +inline bool ParseAndValidateString(const Json::Value& value, std::string* out) { + if (!value.isString()) { + return false; + } + *out = value.asString(); + return true; +} + +// We want to be more robust when we parse fractions then just +// allowing strings, this will parse numeral values such as +// value: 50 as well as value: "50" and value: "100/2". +inline bool ParseAndValidateSimpleFraction(const Json::Value& value, + SimpleFraction* out) { + if (value.isInt()) { + int parsed = value.asInt(); + if (parsed < 0) { + return false; + } + *out = SimpleFraction{parsed, 1}; + return true; + } + + if (value.isString()) { + auto fraction_or_error = SimpleFraction::FromString(value.asString()); + if (!fraction_or_error) { + return false; + } + + if (!fraction_or_error.value().is_positive() || + !fraction_or_error.value().is_defined()) { + return false; + } + *out = std::move(fraction_or_error.value()); + return true; + } + return false; +} + +inline bool ParseAndValidateMilliseconds(const Json::Value& value, + milliseconds* out) { + int out_ms; + if (!ParseAndValidateInt(value, &out_ms) || out_ms < 0) { + return false; + } + *out = milliseconds(out_ms); + return true; +} + +template <typename T> +using Parser = std::function<bool(const Json::Value&, T*)>; + +// NOTE: array parsing methods reset the output vector to an empty vector in +// any error case. This is especially useful for optional arrays. +template <typename T> +bool ParseAndValidateArray(const Json::Value& value, + Parser<T> parser, + std::vector<T>* out) { + out->clear(); + if (!value.isArray() || value.empty()) { + return false; + } + + out->reserve(value.size()); + for (Json::ArrayIndex i = 0; i < value.size(); ++i) { + T v; + if (!parser(value[i], &v)) { + out->clear(); + return false; + } + out->push_back(v); + } + + return true; +} + +inline bool ParseAndValidateIntArray(const Json::Value& value, + std::vector<int>* out) { + return ParseAndValidateArray<int>(value, ParseAndValidateInt, out); +} + +inline bool ParseAndValidateUintArray(const Json::Value& value, + std::vector<uint32_t>* out) { + return ParseAndValidateArray<uint32_t>(value, ParseAndValidateUint, out); +} + +inline bool ParseAndValidateStringArray(const Json::Value& value, + std::vector<std::string>* out) { + return ParseAndValidateArray<std::string>(value, ParseAndValidateString, out); +} + +} // namespace json +} // namespace openscreen + +#endif // UTIL_JSON_JSON_HELPERS_H_ |