ifw-daq  3.0.0-pre2
IFW Data Acquisition modules
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 
18 using Json = nlohmann::json;
19 using JsonPointer = nlohmann::json_pointer<Json>;
20 
21 namespace daq::fits {
22 
23 namespace {
24 
25 template <class>
26 inline constexpr bool AlwaysFalseV = false; // NOLINT
27 
28 template <class T>
29 T GetMember(Json const& json, char const* name, JsonPointer const& breadcrumb);
30 
31 template <>
32 std::string
33 GetMember<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  */
59 template <class KeywordType>
60 KeywordType 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 
99 template <>
100 LiteralKeyword 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 
124 std::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 
130 std::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 
170 nlohmann::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 
200 nlohmann::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:64
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:400
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, int64_t, uint64_t, double, bool > ValueType
Definition: keyword.hpp:243