AK+Everywhere: Remove JsonValue APIs with implicit default values

This commit is contained in:
Dan Klishch 2024-01-12 20:52:38 -05:00 committed by Andrew Kaster
parent c49819cced
commit b5f1a48a7c
Notes: sideshowbarker 2024-07-17 06:39:26 +09:00
16 changed files with 132 additions and 106 deletions

View file

@ -138,16 +138,16 @@ Optional<JsonArray const&> JsonObject::get_array(StringView key) const
Optional<double> JsonObject::get_double_with_precision_loss(StringView key) const Optional<double> JsonObject::get_double_with_precision_loss(StringView key) const
{ {
auto maybe_value = get(key); auto maybe_value = get(key);
if (maybe_value.has_value() && maybe_value->is_number()) if (maybe_value.has_value())
return maybe_value->to_number<double>(); return maybe_value->get_double_with_precision_loss();
return {}; return {};
} }
Optional<float> JsonObject::get_float_with_precision_loss(StringView key) const Optional<float> JsonObject::get_float_with_precision_loss(StringView key) const
{ {
auto maybe_value = get(key); auto maybe_value = get(key);
if (maybe_value.has_value() && maybe_value->is_number()) if (maybe_value.has_value())
return maybe_value->to_number<float>(); return maybe_value->get_float_with_precision_loss();
return {}; return {};
} }
#endif #endif

View file

@ -77,15 +77,31 @@ bool JsonValue::equals(JsonValue const& other) const
if (is_string() && other.is_string() && as_string() == other.as_string()) if (is_string() && other.is_string() && as_string() == other.as_string())
return true; return true;
#if !defined(KERNEL) if (is_number() && other.is_number()) {
if (is_number() && other.is_number() && to_number<double>() == other.to_number<double>()) { auto normalize = [](Variant<u64, i64, double> representation, bool& is_negative) {
return true; return representation.visit(
[&](u64& value) -> Variant<u64, double> {
is_negative = false;
return value;
},
[&](i64& value) -> Variant<u64, double> {
is_negative = value < 0;
return static_cast<u64>(abs(value));
},
[&](double& value) -> Variant<u64, double> {
is_negative = value < 0;
value = abs(value);
if (static_cast<double>(static_cast<u64>(value)) == value)
return static_cast<u64>(value);
return value;
});
};
bool is_this_negative;
auto normalized_this = normalize(as_number(), is_this_negative);
bool is_that_negative;
auto normalized_that = normalize(other.as_number(), is_that_negative);
return is_this_negative == is_that_negative && normalized_this == normalized_that;
} }
#else
if (is_number() && other.is_number() && to_number<i64>() == other.to_number<i64>()) {
return true;
}
#endif
if (is_array() && other.is_array() && as_array().size() == other.as_array().size()) { if (is_array() && other.is_array() && as_array().size() == other.as_array().size()) {
bool result = true; bool result = true;

View file

@ -92,29 +92,29 @@ public:
return serialized<StringBuilder>(); return serialized<StringBuilder>();
} }
int to_int(int default_value = 0) const { return to_i32(default_value); } Optional<int> get_int() const { return get_integer<int>(); }
i32 to_i32(i32 default_value = 0) const { return to_number<i32>(default_value); } Optional<i32> get_i32() const { return get_integer<i32>(); }
i64 to_i64(i64 default_value = 0) const { return to_number<i64>(default_value); } Optional<i64> get_i64() const { return get_integer<i64>(); }
unsigned to_uint(unsigned default_value = 0) const { return to_u32(default_value); } Optional<unsigned> get_uint() const { return get_integer<unsigned>(); }
u32 to_u32(u32 default_value = 0) const { return to_number<u32>(default_value); } Optional<u32> get_u32() const { return get_integer<u32>(); }
u64 to_u64(u64 default_value = 0) const { return to_number<u64>(default_value); } Optional<u64> get_u64() const { return get_integer<u64>(); }
float to_float(float default_value = 0) const { return to_number<float>(default_value); } Optional<float> get_float_with_precision_loss() const { return get_number_with_precision_loss<float>(); }
double to_double(double default_value = 0) const { return to_number<double>(default_value); } Optional<double> get_double_with_precision_loss() const { return get_number_with_precision_loss<double>(); }
FlatPtr to_addr(FlatPtr default_value = 0) const Optional<FlatPtr> get_addr() const
{ {
#ifdef __LP64__ #ifdef __LP64__
return to_u64(default_value); return get_u64();
#else #else
return to_u32(default_value); return get_u32();
#endif #endif
} }
bool to_bool(bool default_value = false) const Optional<bool> get_bool() const
{ {
if (!is_bool()) if (!is_bool())
return default_value; return {};
return as_bool(); return as_bool();
} }
@ -199,19 +199,22 @@ public:
} }
template<typename T> template<typename T>
T to_number(T default_value = 0) const Optional<T> get_number_with_precision_loss() const
{ {
if (type() == Type::Double) switch (m_type) {
return (T)m_value.as_double; case Type::Double:
if (type() == Type::Int32) return static_cast<T>(m_value.as_double);
return (T)m_value.as_i32; case Type::Int32:
if (type() == Type::UnsignedInt32) return static_cast<T>(m_value.as_i32);
return (T)m_value.as_u32; case Type::UnsignedInt32:
if (type() == Type::Int64) return static_cast<T>(m_value.as_u32);
return (T)m_value.as_i64; case Type::Int64:
if (type() == Type::UnsignedInt64) return static_cast<T>(m_value.as_i64);
return (T)m_value.as_u64; case Type::UnsignedInt64:
return default_value; return static_cast<T>(m_value.as_u64);
default:
return {};
}
} }
template<Integral T> template<Integral T>
@ -250,6 +253,14 @@ public:
} }
} }
template<Integral T>
Optional<T> get_integer() const
{
if (!is_integer<T>())
return {};
return as_integer<T>();
}
bool equals(JsonValue const& other) const; bool equals(JsonValue const& other) const;
private: private:

View file

@ -170,7 +170,7 @@ Variants read_variants_settings(JsonObject const& variants_obj)
if (variants_obj.has_array("argument_counts"sv)) { if (variants_obj.has_array("argument_counts"sv)) {
variants.argument_counts.clear_with_capacity(); variants.argument_counts.clear_with_capacity();
variants_obj.get_array("argument_counts"sv)->for_each([&](auto const& argument_count_value) { variants_obj.get_array("argument_counts"sv)->for_each([&](auto const& argument_count_value) {
variants.argument_counts.append(argument_count_value.to_u32()); variants.argument_counts.append(argument_count_value.get_u32().value());
}); });
} }
if (variants_obj.has_array("argument_defaults"sv)) { if (variants_obj.has_array("argument_defaults"sv)) {

View file

@ -133,28 +133,30 @@ TEST_CASE(json_parse_empty_string)
TEST_CASE(json_parse_long_decimals) TEST_CASE(json_parse_long_decimals)
{ {
auto value = JsonValue::from_string("1644452550.6489999294281"sv); auto value = JsonValue::from_string("1644452550.6489999294281"sv);
EXPECT_EQ(value.value().to_number<double>(), 1644452550.6489999294281); EXPECT_EQ(value.value().get_double_with_precision_loss(), 1644452550.6489999294281);
} }
TEST_CASE(json_parse_number_with_exponent) TEST_CASE(json_parse_number_with_exponent)
{ {
auto value_without_fraction = JsonValue::from_string("10e5"sv); auto value_without_fraction = JsonValue::from_string("10e5"sv);
EXPECT_EQ(value_without_fraction.value().to_number<double>(), 1000000.0); EXPECT_EQ(value_without_fraction.value().get_double_with_precision_loss(), 1000000.0);
auto value_with_fraction = JsonValue::from_string("10.5e5"sv); auto value_with_fraction = JsonValue::from_string("10.5e5"sv);
EXPECT_EQ(value_with_fraction.value().to_number<double>(), 1050000.0); EXPECT_EQ(value_with_fraction.value().get_double_with_precision_loss(), 1050000.0);
} }
TEST_CASE(json_parse_special_numbers) TEST_CASE(json_parse_special_numbers)
{ {
#define EXPECT_TO_MATCH_NUMBER_BIT_WISE(string_input, double_input) \ #define EXPECT_TO_MATCH_NUMBER_BIT_WISE(string_input, double_input) \
do { \ do { \
auto value_or_error = JsonValue::from_string(string_input##sv); \ auto value_or_error = JsonValue::from_string(string_input##sv); \
VERIFY(!value_or_error.is_error()); \ VERIFY(!value_or_error.is_error()); \
if (value_or_error.is_error()) \ if (value_or_error.is_error()) \
dbgln("got {}", value_or_error.error()); \ dbgln("got {}", value_or_error.error()); \
EXPECT(value_or_error.value().is_number()); \ auto value = value_or_error.release_value(); \
EXPECT_EQ(bit_cast<u64>(value_or_error.value().to_double(4321.0)), bit_cast<u64>(static_cast<double>(double_input))); \ EXPECT(value.is_number()); \
auto value_as_double = value.get_double_with_precision_loss().value(); \
EXPECT_EQ(bit_cast<u64>(value_as_double), bit_cast<u64>(static_cast<double>(double_input))); \
} while (false) } while (false)
EXPECT_TO_MATCH_NUMBER_BIT_WISE("-0", -0.); EXPECT_TO_MATCH_NUMBER_BIT_WISE("-0", -0.);

View file

@ -47,10 +47,11 @@ void UsersMapWidget::get_users()
auto json_users = result.release_value().as_array(); auto json_users = result.release_value().as_array();
for (size_t i = 0; i < json_users.size(); i++) { for (size_t i = 0; i < json_users.size(); i++) {
auto const& json_user = json_users.at(i).as_object(); auto const& json_user = json_users.at(i).as_object();
auto const& coordinates = json_user.get_array("coordinates"sv).release_value();
User user { User user {
MUST(String::from_byte_string(json_user.get_byte_string("nick"sv).release_value())), MUST(String::from_byte_string(json_user.get_byte_string("nick"sv).release_value())),
{ json_user.get_array("coordinates"sv).release_value().at(0).to_double(), { coordinates[0].get_double_with_precision_loss().value(),
json_user.get_array("coordinates"sv).release_value().at(1).to_double() }, coordinates[1].get_double_with_precision_loss().value() },
json_user.has_bool("contributor"sv), json_user.has_bool("contributor"sv),
}; };
m_users.value().append(user); m_users.value().append(user);

View file

@ -1469,7 +1469,7 @@ ImageEditor& MainWidget::create_new_editor(NonnullRefPtr<Image> image)
else else
return; return;
image_editor.add_guide(PixelPaint::Guide::construct(orientation, offset_value->to_number<float>())); image_editor.add_guide(PixelPaint::Guide::construct(orientation, offset_value->get_float_with_precision_loss().value_or(0)));
}); });
} }

View file

@ -149,7 +149,7 @@ ErrorOr<Gfx::IntSize> Presentation::parse_presentation_size(JsonObject const& me
return Error::from_string_view("Width or aspect in incorrect format"sv); return Error::from_string_view("Width or aspect in incorrect format"sv);
// We intentionally discard floating-point data here. If you need more resolution, just use a larger width. // We intentionally discard floating-point data here. If you need more resolution, just use a larger width.
auto const width = maybe_width->to_number<int>(); auto const width = maybe_width->get_number_with_precision_loss<int>().value();
auto const aspect_parts = maybe_aspect->split_view(':'); auto const aspect_parts = maybe_aspect->split_view(':');
if (aspect_parts.size() != 2) if (aspect_parts.size() != 2)
return Error::from_string_view("Aspect specification must have the exact format `width:height`"sv); return Error::from_string_view("Aspect specification must have the exact format `width:height`"sv);

View file

@ -8,6 +8,7 @@
#include "Presentation.h" #include "Presentation.h"
#include <AK/JsonObject.h> #include <AK/JsonObject.h>
#include <AK/URL.h> #include <AK/URL.h>
#include <LibGUI/PropertyDeserializer.h>
#include <LibGfx/Font/FontStyleMapping.h> #include <LibGfx/Font/FontStyleMapping.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
@ -42,16 +43,8 @@ ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject c
void SlideObject::set_property(StringView name, JsonValue value) void SlideObject::set_property(StringView name, JsonValue value)
{ {
if (name == "rect"sv) { if (name == "rect"sv)
if (value.is_array() && value.as_array().size() == 4) { m_rect = GUI::PropertyDeserializer<Gfx::IntRect> {}(value).release_value_but_fixme_should_propagate_errors();
Gfx::IntRect rect;
rect.set_x(value.as_array()[0].to_i32());
rect.set_y(value.as_array()[1].to_i32());
rect.set_width(value.as_array()[2].to_i32());
rect.set_height(value.as_array()[3].to_i32());
m_rect = rect;
}
}
m_properties.set(name, move(value)); m_properties.set(name, move(value));
} }
@ -74,7 +67,7 @@ void Text::set_property(StringView name, JsonValue value)
} else if (name == "font-weight"sv) { } else if (name == "font-weight"sv) {
m_font_weight = Gfx::name_to_weight(value.as_string()); m_font_weight = Gfx::name_to_weight(value.as_string());
} else if (name == "font-size"sv) { } else if (name == "font-size"sv) {
m_font_size_in_pt = value.to_float(); m_font_size_in_pt = value.get_float_with_precision_loss().value();
} else if (name == "text-alignment"sv) { } else if (name == "text-alignment"sv) {
m_text_align = value.as_string(); m_text_align = value.as_string();
} }

View file

@ -479,7 +479,7 @@ ErrorOr<NonnullOwnPtr<Profile>> Profile::load_from_perfcore_file(StringView path
auto const& stack_array = stack.value(); auto const& stack_array = stack.value();
for (ssize_t i = stack_array.values().size() - 1; i >= 0; --i) { for (ssize_t i = stack_array.values().size() - 1; i >= 0; --i) {
auto const& frame = stack_array.at(i); auto const& frame = stack_array.at(i);
auto ptr = frame.to_number<u64>(); auto ptr = frame.as_integer<u64>();
u32 offset = 0; u32 offset = 0;
DeprecatedFlyString object_name; DeprecatedFlyString object_name;
ByteString symbol; ByteString symbol;

View file

@ -66,16 +66,10 @@ ErrorOr<Gfx::IntRect> PropertyDeserializer<Gfx::IntRect>::operator()(JsonValue c
} else { } else {
auto const& array = value.as_array(); auto const& array = value.as_array();
auto get_i32 = [](JsonValue const& value) -> Optional<int> { x = array[0].get_i32();
if (value.is_integer<i32>()) y = array[1].get_i32();
return value.to_i32(); width = array[2].get_i32();
return {}; height = array[3].get_i32();
};
x = get_i32(array[0]);
y = get_i32(array[1]);
width = get_i32(array[2]);
height = get_i32(array[3]);
} }
if (!x.has_value()) if (!x.has_value())
@ -103,16 +97,16 @@ ErrorOr<Gfx::IntSize> PropertyDeserializer<Gfx::IntSize>::operator()(JsonValue c
auto const& array = value.as_array(); auto const& array = value.as_array();
auto const& width = array[0]; auto const& width = array[0].get_i32();
if (!width.is_integer<i32>()) if (!width.has_value())
return Error::from_string_literal("Width must be an integer"); return Error::from_string_literal("Width must be an integer");
auto const& height = array[1]; auto const& height = array[1].get_i32();
if (!height.is_integer<i32>()) if (!height.has_value())
return Error::from_string_literal("Height must be an integer"); return Error::from_string_literal("Height must be an integer");
Gfx::IntSize size; Gfx::IntSize size;
size.set_width(width.to_i32()); size.set_width(width.value());
size.set_height(height.to_i32()); size.set_height(height.value());
return size; return size;
} }
@ -129,10 +123,10 @@ ErrorOr<GUI::Margins> PropertyDeserializer<GUI::Margins>::operator()(JsonValue c
int m[4]; int m[4];
for (size_t i = 0; i < size; ++i) { for (size_t i = 0; i < size; ++i) {
auto const& margin = array[i]; auto const& margin = array[i].get_i32();
if (!margin.is_integer<i32>()) if (!margin.has_value())
return Error::from_string_literal("Margin value should be an integer"); return Error::from_string_literal("Margin value should be an integer");
m[i] = margin.to_i32(); m[i] = margin.value();
} }
if (size == 1) if (size == 1)

View file

@ -23,7 +23,7 @@ struct PropertyDeserializer<T> {
{ {
if (!value.is_integer<T>()) if (!value.is_integer<T>())
return Error::from_string_literal("Value is either not an integer or out of range for requested type"); return Error::from_string_literal("Value is either not an integer or out of range for requested type");
return value.to_number<T>(); return value.as_integer<T>();
} }
}; };

View file

@ -434,8 +434,8 @@ Value JSONObject::parse_json_value(VM& vm, JsonValue const& value)
return Value(parse_json_array(vm, value.as_array())); return Value(parse_json_array(vm, value.as_array()));
if (value.is_null()) if (value.is_null())
return js_null(); return js_null();
if (value.is_number()) if (auto double_value = value.get_double_with_precision_loss(); double_value.has_value())
return Value(value.to_double(0)); return Value(double_value.value());
if (value.is_string()) if (value.is_string())
return PrimitiveString::create(vm, value.as_string()); return PrimitiveString::create(vm, value.as_string());
if (value.is_bool()) if (value.is_bool())

View file

@ -175,7 +175,7 @@ Vector<Symbol> symbolicate_thread(pid_t pid, pid_t tid, IncludeSourcePosition in
stack.ensure_capacity(json.value().as_array().size()); stack.ensure_capacity(json.value().as_array().size());
for (auto& value : json.value().as_array().values()) { for (auto& value : json.value().as_array().values()) {
stack.append(value.to_addr()); stack.append(value.get_addr().value());
} }
} }

View file

@ -51,13 +51,24 @@ ErrorOr<TimeoutsConfiguration, Error> json_deserialize_as_a_timeouts_configurati
auto script_duration = value.as_object().get("script"sv); auto script_duration = value.as_object().get("script"sv);
// 2. If script duration is a number and less than 0 or greater than maximum safe integer, or it is not null, return error with error code invalid argument. // 2. If script duration is a number and less than 0 or greater than maximum safe integer, or it is not null, return error with error code invalid argument.
if (script_duration.has_value() && script_duration->is_number() && (script_duration->to_i64() < 0 || script_duration->to_i64() > max_safe_integer)) Optional<u64> script_timeout;
return Error::from_code(ErrorCode::InvalidArgument, "Invalid script duration"); if (script_duration.has_value()) {
if (script_duration.has_value() && !script_duration->is_number() && !script_duration->is_null()) bool is_valid;
return Error::from_code(ErrorCode::InvalidArgument, "Invalid script duration"); if (auto duration = script_duration->get_double_with_precision_loss(); duration.has_value()) {
is_valid = *duration >= 0 && *duration <= max_safe_integer;
// FIXME: script_timeout should be double.
script_timeout = static_cast<u64>(*duration);
} else if (script_duration->is_null()) {
is_valid = true;
} else {
is_valid = false;
}
if (!is_valid)
return Error::from_code(ErrorCode::InvalidArgument, "Invalid script duration");
}
// 3. Set timeoutss script timeout to script duration. // 3. Set timeoutss script timeout to script duration.
timeouts.script_timeout = (!script_duration.has_value() || script_duration->is_null()) ? Optional<u64> {} : script_duration->to_u64(); timeouts.script_timeout = script_timeout;
} }
// 4. If value has a property with the key "pageLoad": // 4. If value has a property with the key "pageLoad":

View file

@ -247,9 +247,9 @@ static ErrorOr<PropertyType, Web::WebDriver::Error> get_property(JsonValue const
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Boolean", key)); return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Boolean", key));
return property->as_bool(); return property->as_bool();
} else if constexpr (IsSame<PropertyType, u32>) { } else if constexpr (IsSame<PropertyType, u32>) {
if (!property->is_integer<u32>()) if (auto maybe_u32 = property->get_u32(); maybe_u32.has_value())
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Number", key)); return *maybe_u32;
return property->to_u32(); return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Number", key));
} else if constexpr (IsSame<PropertyType, JsonArray const*>) { } else if constexpr (IsSame<PropertyType, JsonArray const*>) {
if (!property->is_array()) if (!property->is_array())
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not an Array", key)); return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not an Array", key));
@ -617,20 +617,18 @@ Messages::WebDriverClient::SetWindowRectResponse WebDriverConnection::set_window
auto const& properties = payload.as_object(); auto const& properties = payload.as_object();
auto resolve_property = [](auto name, auto const& property, auto min, auto max) -> ErrorOr<Optional<i32>, Web::WebDriver::Error> { auto resolve_property = [](auto name, auto const& property, i32 min, i32 max) -> ErrorOr<Optional<i32>, Web::WebDriver::Error> {
if (property.is_null()) if (property.is_null())
return Optional<i32> {}; return Optional<i32> {};
if (!property.is_number()) auto value = property.template get_integer<i32>();
if (!value.has_value())
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Number", name)); return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' is not a Number", name));
if (*value < min)
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' value {} exceeds the minimum allowed value {}", name, *value, min));
if (*value > max)
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' value {} exceeds the maximum allowed value {}", name, *value, max));
auto number = property.template to_number<i64>(); return value;
if (number < min)
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' value {} exceeds the minimum allowed value {}", name, number, min));
if (number > max)
return Web::WebDriver::Error::from_code(Web::WebDriver::ErrorCode::InvalidArgument, ByteString::formatted("Property '{}' value {} exceeds the maximum allowed value {}", name, number, max));
return static_cast<i32>(number);
}; };
// 1. Let width be the result of getting a property named width from the parameters argument, else let it be null. // 1. Let width be the result of getting a property named width from the parameters argument, else let it be null.