$darkmode
sol.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
32 #pragma once
33 
34 #include <nlohmann/json.hpp>
35 
36 #include <sol/state_view.hpp>
37 #include <sol/object.hpp> // for object
38 #include <sol/optional.hpp> // for optional
39 #include <sol/table.hpp> // for table
40 #include <sol/version.hpp> // for SOL_IS_OFF, SOL_SAFE_NUMERICS, ...
41 
42 #if SOL_IS_OFF(SOL_SAFE_NUMERICS) && SOL_IS_OFF(SOL_ALL_SAFETIES_ON)
43 #error "nlohmann support for sol requires at least SOL_SAFE_NUMERICS=1"
44 #include <force_compiler_to_stop_here>
45 #endif
46 
47 namespace nlohmann {
48 
60 template <>
61 struct adl_serializer<sol::object> {
62  // NOLINTNEXTLINE(misc-no-recursion)
63  static void to_json_array(json& j, const sol::table& tbl) {
64  if (j.type() != json::value_t::array) {
65  j = json::array();
66  }
67  auto kv_args = json::object();
68  for (auto& kv : tbl) {
69  if (kv.first.get_type() != sol::type::number) {
70  auto key = kv.first.as<std::string>();
71  to_json(kv_args[key], kv.second.as<sol::object>());
72  continue;
73  }
74  j.emplace_back(kv.second.as<sol::object>());
75  }
76  if (!kv_args.empty()) {
77  j.emplace_back(std::move(kv_args));
78  }
79  }
80 
81  // NOLINTNEXTLINE(misc-no-recursion)
82  static void to_json_object(json& j, const sol::table& tbl) {
83  if (j.type() != json::value_t::object) {
84  j = json::object();
85  }
86  for (auto& kv : tbl) {
87  auto key = kv.first.as<std::string>();
88  to_json(j[key], kv.second.as<sol::object>());
89  }
90  }
91 
92  // NOLINTNEXTLINE(misc-no-recursion)
93  static void to_json(json& j, const sol::table& tbl) {
94  if (tbl.pairs().begin() == tbl.pairs().end()) {
95  // We don't know whether this is an empty array or an empty object,
96  // but it's probably an array since this makes more sense to have.
97  if (j.type() == json::value_t::null) {
98  j = json::array();
99  }
100  return;
101  }
102 
103  // Lua only accepts table keys that are integers or strings,
104  // but they can be mixed; we use the first element to guess
105  // whether its an array or an object.
106  auto first = (*tbl.pairs().begin()).first;
107  bool looks_like_array = (first.get_type() == sol::type::number);
108  if (looks_like_array) {
109  to_json_array(j, tbl);
110  } else {
111  to_json_object(j, tbl);
112  }
113  }
114 
115  // NOLINTNEXTLINE(misc-no-recursion)
116  static void to_json(json& j, const sol::object& obj) {
117  switch (obj.get_type()) {
118  case sol::type::table: {
119  to_json(j, obj.as<sol::table>());
120  break;
121  }
122  case sol::type::string: {
123  j = obj.as<std::string>();
124  break;
125  }
126  case sol::type::boolean: {
127  j = obj.as<bool>();
128  break;
129  }
130  case sol::type::number: {
131  // If the number in Lua has any significant decimals, even if they are zero,
132  // it is not an integer and this optional will be falsy.
133  if (auto num = obj.as<sol::optional<int64_t>>(); num) {
134  j = *num;
135  } else {
136  j = obj.as<double>();
137  }
138  break;
139  }
140  case sol::type::nil:
141  case sol::type::none: {
142  j = nullptr;
143  break;
144  }
145  case sol::type::poly:
146  // throw std::out_of_range("cannot serialize lua poly type to JSON");
147  j = "<poly>";
148  break;
149  case sol::type::function:
150  // throw std::out_of_range("cannot serialize lua function to JSON");
151  j = "<function>";
152  break;
153  case sol::type::thread:
154  // throw std::out_of_range("cannot serialize lua thread to JSON");
155  j = "<thread>";
156  break;
157  case sol::type::userdata:
158  case sol::type::lightuserdata:
159  j = "<userdata>";
160  break;
161  }
162  }
163 
164  // NOLINTNEXTLINE(misc-no-recursion)
165  static void from_json(const json& j, sol::object& obj) {
166  auto* L = obj.lua_state();
167  if (L == nullptr) {
168  throw std::logic_error("can only deserialize to existing sol::object");
169  }
170  auto lua = sol::state_view(L);
171  switch (j.type()) {
172  case json::value_t::object: {
173  auto tbl = lua.create_table();
174  for (const auto& it : j.items()) {
175  auto tmp = sol::object(L, sol::in_place, nullptr);
176  from_json(it.value(), tmp);
177  tbl[it.key()] = tmp;
178  }
179  obj = tbl;
180  break;
181  }
182  case json::value_t::null: {
183  obj = sol::object(L, sol::in_place, nullptr);
184  break;
185  }
186  case json::value_t::array: {
187  auto tbl = lua.create_table();
188  for (const auto& el : j) {
189  auto tmp = sol::object(L, sol::in_place, nullptr);
190  from_json(el, tmp);
191  tbl.add(tmp);
192  }
193  obj = tbl;
194  break;
195  }
196  case json::value_t::binary: {
197  obj = sol::object(L, sol::in_place, nullptr);
198  break;
199  }
200  case json::value_t::string: {
201  obj = sol::object(L, sol::in_place, j.get<std::string>());
202  break;
203  }
204  case json::value_t::boolean: {
205  obj = sol::object(L, sol::in_place, j.get<bool>());
206  break;
207  }
208  case json::value_t::number_float: {
209  obj = sol::object(L, sol::in_place, j.get<double>());
210  break;
211  }
212  case json::value_t::number_unsigned: {
213  obj = sol::object(L, sol::in_place, j.get<unsigned long>());
214  break;
215  }
216  case json::value_t::number_integer: {
217  obj = sol::object(L, sol::in_place, j.get<signed long>());
218  break;
219  }
220  case json::value_t::discarded: {
221  obj = sol::object(L, sol::in_place, nullptr);
222  break;
223  }
224  }
225  }
226 };
227 
228 } // namespace nlohmann
229 
230 namespace fable {
231 
232 inline sol::object into_sol_object(sol::state_view& lua, const nlohmann::json& json) {
233  auto tmp = sol::object(lua, sol::in_place, nullptr);
234  nlohmann::adl_serializer<sol::object>::from_json(json, tmp);
235  return tmp;
236 }
237 
238 inline sol::object into_sol_object(sol::this_state& state, const nlohmann::json& json) {
239  auto lua = sol::state_view(state);
240  auto tmp = sol::object(lua, sol::in_place, nullptr);
241  nlohmann::adl_serializer<sol::object>::from_json(json, tmp);
242  return tmp;
243 }
244 
245 } // namespace fable