ifw-daq 3.1.0
IFW Data Acquisition modules
Loading...
Searching...
No Matches
json.cpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup daq_ocm_libfits
4 * @copyright 2022 ESO - European Southern Observatory
5 *
6 * @brief Definition of contents from fits/json.hpp
7 */
8#include <boost/assign.hpp>
9#include <boost/range/adaptor/indexed.hpp>
10
11#include <daq/error/json.hpp>
12#include <daq/fits/json.hpp>
13#include <fmt/format.h>
14#include <nlohmann/json.hpp>
15
16#include <iostream>
17
18using Json = nlohmann::json;
19using JsonPointer = nlohmann::json_pointer<Json>;
20
21namespace daq::fits {
22
23namespace {
24
25template <class>
26inline constexpr bool AlwaysFalseV = false; // NOLINT
27
28template <class T>
29T GetMember(Json const& json, char const* name, JsonPointer const& breadcrumb);
30
31template <>
32std::string
33GetMember<std::string>(Json const& json, char const* name, JsonPointer const& breadcrumb) {
34 if (!json.contains(name)) {
35 throw error::MakeJsonErrorMissingValue<std::invalid_argument>(breadcrumb / name);
36 }
37 auto const& value_json = json[name];
38 if (!value_json.is_string()) {
39 throw error::MakeJsonErrorWrongType<std::invalid_argument>(
40 breadcrumb / name, "string", value_json.type_name());
41 }
42 auto value = value_json.get<std::string>();
43 if (value.empty()) {
44 throw error::MakeJsonError<std::invalid_argument>(breadcrumb / name,
45 "empty string is invalid");
46 }
47 return value;
48}
49
50/**
51 * Parse the JSON object that defines a keyword.
52 * It requires that @a obj is a JSON object with the following fields:
53 * - name:str
54 * - value:bool,str,number
55 * - [comment:str] (optional)
56 *
57 * @pre @a `obj.is_object() == true`
58 */
59template <class KeywordType>
60KeywordType ParseKeywordObject(Json const& obj, JsonPointer const& breadcrumb) {
61 if (!obj.is_object()) {
62 throw error::MakeJsonErrorWrongType<std::invalid_argument>(
63 breadcrumb, "object", obj.type_name());
64 }
65
66 auto name = GetMember<std::string>(obj, "name", breadcrumb);
67 if (!obj.contains("value")) {
68 throw error::MakeJsonErrorMissingValue<std::invalid_argument>(breadcrumb);
69 }
70
71 typename KeywordType::ValueType kw_value;
72 auto const& value = obj["value"];
73 if (value.is_boolean()) {
74 kw_value = value.get<bool>();
75 } else if (value.is_string()) {
76 kw_value = value.get<std::string>();
77 } else if (value.is_number_float()) {
78 kw_value = value.get<double>();
79 } else if (value.is_number_unsigned()) {
80 kw_value = value.get<uint64_t>();
81 } else if (value.is_number_integer()) {
82 kw_value = value.get<int64_t>();
83 } else {
84 throw std::invalid_argument(fmt::format("unsupported type: {}", value.type_name()));
85 }
86
87 std::optional<std::string> kw_comment;
88 if (obj.contains("comment")) {
89 auto const& comment = obj["comment"];
90 if (!comment.is_string()) {
91 throw error::MakeJsonErrorWrongType<std::invalid_argument>(
92 breadcrumb / "comment", "string", comment.type_name());
93 }
94 kw_comment = comment.get<std::string>();
95 }
96 return KeywordType(name, kw_value, kw_comment);
97}
98
99template <>
100LiteralKeyword ParseKeywordObject<LiteralKeyword>(Json const& obj, JsonPointer const& breadcrumb) {
101 assert(obj.is_object());
102
103 if (!obj.contains("value")) {
104 throw std::invalid_argument("field 'value' missing");
105 }
106
107 auto const& value = obj["value"];
108 if (!value.is_string()) {
109 throw std::invalid_argument(
110 fmt::format("literalKeyword value must be a string got: {}", value.type_name()));
111 }
112 auto const& value_str = value.get<std::string>();
113 if (value_str.length() > constants::RECORD_LENGTH) {
114 throw std::invalid_argument(
115 fmt::format("literalKeyword value longer than maximum keyword record. Max: {} got: {}",
116 value_str.length(),
117 constants::RECORD_LENGTH));
118 }
119 return LiteralKeyword(std::string_view(value_str));
120}
121
122} // namespace
123
124std::vector<KeywordVariant> ParseJsonKeywords(char const* keywords) {
125 // outer value must be an array
126 auto parsed = Json::parse(keywords);
127 return ParseJsonKeywords(parsed);
128}
129
130std::vector<KeywordVariant> ParseJsonKeywords(Json const& keywords, JsonPointer const& breadcrumb) {
131 using namespace boost::assign;
132 using namespace boost::adaptors;
133
134 if (!keywords.is_array()) {
135 throw error::MakeJsonErrorWrongType<std::invalid_argument>(
136 breadcrumb, "array", keywords.type_name());
137 }
138
139 std::vector<KeywordVariant> parsed_keywords;
140 parsed_keywords.reserve(keywords.size());
141
142 for (auto const& index : keywords | indexed(0u)) {
143 auto const& kw_obj = index.value();
144 auto breadcrumb_idx = breadcrumb / index.index();
145 if (!kw_obj.is_object()) {
146 throw error::MakeJsonErrorWrongType<std::invalid_argument>(
147 breadcrumb_idx, "object", kw_obj.type_name());
148 }
149 auto type = GetMember<std::string>(kw_obj, "type", breadcrumb_idx);
150 if (type == "valueKeyword") {
151 parsed_keywords.emplace_back(ParseKeywordObject<ValueKeyword>(kw_obj, breadcrumb_idx));
152 } else if (type == "esoKeyword") {
153 parsed_keywords.emplace_back(ParseKeywordObject<EsoKeyword>(kw_obj, breadcrumb_idx));
154 } else if (type == "literalKeyword") {
155 parsed_keywords.emplace_back(
156 ParseKeywordObject<LiteralKeyword>(kw_obj, breadcrumb_idx));
157 } else {
158 throw error::MakeJsonErrorUnknownVariant<std::invalid_argument>(
159 breadcrumb_idx, "'valueKeyword', 'esoKeyword' or 'literalKeyword'", type.c_str());
160 }
161 }
162
163 return parsed_keywords;
164}
165
167 return std::visit([&](auto const& val) -> Json { return nlohmann::json(val); }, value);
168}
169
170nlohmann::json SerializeJsonKeyword(KeywordVariant const& keyword) {
171 using Json = nlohmann::json;
172 return std::visit(
173 [&](auto const& kw) -> Json {
174 using T = std::decay_t<decltype(kw)>;
175 if constexpr (std::is_same_v<T, ValueKeyword>) {
176 auto j = Json::object({{"type", "valueKeyword"},
177 {"name", kw.name},
178 {"value", SerializeJsonKeywordValue(kw.value)}});
179 if (kw.comment) {
180 j["comment"] = *kw.comment;
181 }
182 return j;
183 } else if constexpr (std::is_same_v<T, EsoKeyword>) {
184 auto j = Json::object({{"type", "esoKeyword"},
185 {"name", kw.name},
186 {"value", SerializeJsonKeywordValue(kw.value)}});
187 if (kw.comment) {
188 j["comment"] = *kw.comment;
189 }
190 return j;
191 } else if constexpr (std::is_same_v<T, LiteralKeyword>) {
192 return Json::object({{"type", "literalKeyword"}, {"value", kw.GetRecord()}});
193 } else {
194 static_assert(AlwaysFalseV<T>, "non-exhaustive visitor!");
195 }
196 },
197 keyword);
198}
199
200nlohmann::json SerializeJsonKeywords(std::vector<KeywordVariant> const& keywords) {
201 auto arr = nlohmann::json::array();
202 for (auto const& kw : keywords) {
203 arr.push_back(SerializeJsonKeyword(kw));
204 }
205 return arr;
206}
207
208} // namespace daq::fits
Contains data structure for FITS keywords.
nlohmann::json_pointer< Json > JsonPointer
Definition: json.cpp:19
nlohmann::json Json
Definition: json.cpp:18
nlohmann::json SerializeJsonKeywords(std::vector< KeywordVariant > const &keywords)
SerializeJsons keyword to JSON.
Definition: json.cpp:200
KeywordType
Type of FITS keyword.
Definition: keyword.hpp:68
nlohmann::json SerializeJsonKeywordValue(BasicKeywordBase::ValueType const &value)
SerializeJsons the keyword value variant to JSON.
Definition: json.cpp:166
std::variant< ValueKeyword, EsoKeyword, LiteralKeyword > KeywordVariant
The different variants of keywords that are supported.
Definition: keyword.hpp:409
std::vector< KeywordVariant > ParseJsonKeywords(char const *keywords)
Parse and return FITS keywords.
Definition: json.cpp:124
nlohmann::json SerializeJsonKeyword(KeywordVariant const &keyword)
SerializeJsons keyword to JSON.
Definition: json.cpp:170
std::pair< Json const &, JsonPointer > GetMember(Json const &json, char const *name, JsonPointer const &breadcrumb)
std::variant< std::string, std::int64_t, std::uint64_t, double, bool > ValueType
Definition: keyword.hpp:252