Skip to content

Commit b413383

Browse files
authored
Improve generic factory pair readers (#82964)
* Revert "Generic reader for vector of pairs" This reverts commit 991994f. * Improve generic factory pair readers Document what JSON formats the readers accept, and modify pair_reader so that it can accept arbitrary types. Add a new pair type { "a": b } that this pair_reader supports, and expand key_from_json_string to enable testing if the type of a can support this.
1 parent c979805 commit b413383

File tree

3 files changed

+72
-59
lines changed

3 files changed

+72
-59
lines changed

src/generic_factory.h

Lines changed: 63 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "flexbuffer_json.h"
3333
#include "init.h"
3434
#include "int_id.h"
35+
#include "json.h"
3536
#include "mod_tracker.h"
3637
#include "string_formatter.h"
3738
#include "string_id.h"
@@ -1908,13 +1909,19 @@ class string_id_reader : public generic_typed_reader<string_id_reader<T>>
19081909

19091910
/**
19101911
* Loads std::pair of [K = string_id, V = int/float] values from JSON -- usually into an std::map
1912+
* It can load either an array, an object, or a single value
19111913
* Accepted formats for elements in an array:
19121914
* 1. A named key/value pair object: "addiction_type": [ { "addiction": "caffeine", "potential": 3 } ]
19131915
* 2. A key/value pair array: "addiction_type": [ [ "caffeine", 3 ] ]
19141916
* 3. A single value: "addiction_type": [ "caffeine" ]
1915-
* A single value can also be provided outside of an array, e.g. "addiction_type": "caffeine"
1917+
* For an object, only the following format is supported
1918+
* 4. "addiction_type": { "caffeine": 3, "alcohol": 2 }
1919+
* A single value can be provided as follows:
1920+
* 5. "addiction_type": "caffeine"
19161921
* For single values, weights are assigned default_weight
19171922
*/
1923+
// TODO: format 1 is quite gross, and just accepts arbitrary keys. The only way it works is by limiting
1924+
// the types of V, which is unfortunate
19181925
template<typename K, typename V>
19191926
class weighted_string_id_reader : public generic_typed_reader<weighted_string_id_reader<K, V>>
19201927
{
@@ -1966,87 +1973,86 @@ class weighted_string_id_reader : public generic_typed_reader<weighted_string_id
19661973
}
19671974
};
19681975

1976+
// For loading pairs from a JSON object. { "a1": b1, "a2": b2 }, etc
1977+
// Only supports JSON objects! Json objects are not ordered, so if you're using something where
1978+
// the order matters, use pair_reader.
19691979
template<typename K, typename V>
19701980
class generic_map_reader : public generic_typed_reader<generic_map_reader<K, V>>
19711981
{
19721982
public:
19731983
static constexpr bool read_objects = true;
19741984

1985+
static_assert( key_from_json_string<K>::valid, "Type K must have a known conversion from string" );
1986+
19751987
std::pair<K, V> get_next( const JsonValue &jv ) const {
19761988
const JsonMember *jm = dynamic_cast<const JsonMember *>( &jv );
19771989
if( jm == nullptr ) {
19781990
jv.throw_error( "not part of a JsonObject" );
19791991
}
1980-
K key( jm->name() );
1992+
K key = key_from_json_string<K>()( jm->name() );
19811993
V value;
19821994
jv.read( value, true );
19831995
return std::pair<K, V>( key, value );
19841996
}
19851997
};
19861998

1987-
// Support shorthand for a single value.
1988-
template<typename T>
1989-
class pair_reader : public generic_typed_reader<pair_reader<T>>
1990-
{
1991-
public:
1992-
std::pair<T, T> get_next( const JsonValue &jv ) const {
1993-
if( jv.test_float() ) {
1994-
T val;
1995-
jv.read( val, true );
1996-
return std::make_pair( val, val );
1997-
}
1998-
if( !jv.test_array() ) {
1999-
jv.throw_error( "bad pair" );
2000-
}
2001-
JsonArray ja = jv.get_array();
2002-
if( ja.size() != 2 ) {
2003-
ja.throw_error( "Must have 2 elements" );
2004-
}
2005-
T l;
2006-
T h;
2007-
ja[0].read( l, true );
2008-
ja[1].read( h, true );
2009-
return std::make_pair( l, h );
2010-
}
2011-
};
2012-
2013-
// Reads into a vector of pairs in the format (example)
2014-
// "growth_stages": [ { "GROWTH_SEED": "23 days" }, { "GROWTH_SEEDLING": "23 days" }, { "GROWTH_MATURE": "23 days" }, { "GROWTH_HARVEST": "22 days" } ]
2015-
// OR
2016-
// "growth_stages": [ [ "GROWTH_SEED", "23 days" ], [ "GROWTH_SEEDLING", "23 days" ], [ "GROWTH_MATURE", "23 days" ], [ "GROWTH_HARVEST", "22 days" ] ]
2017-
// The key(K) of the pair object must be string constructable.
2018-
template<typename K, typename V>
2019-
class vector_pair_reader : public generic_typed_reader<vector_pair_reader<K, V>>
1999+
// Supports three formats:
2000+
// 1. [ a, b ]
2001+
// 2. { "a": b }, if there exists a key_from_json_string specialization for a
2002+
// 3. a, if T = U and a is not represented in JSON as an array or object (iff the object format is valid)
2003+
// This would load [ "a", [ "b", "c" ], { "d": "e" } ] into three pairs:
2004+
// ("a", "a"), ("b", "c"), ("d", "e")
2005+
//
2006+
// If you only need format 1, you can also use json_read_reader<std::pair<T, U>>
2007+
//
2008+
// TODO: constructor argument to enable/disable the shorthand format?
2009+
template<typename T, typename U = T>
2010+
class pair_reader : public generic_typed_reader<pair_reader<T, U>>
20202011
{
20212012
public:
2022-
std::pair<K, V> get_next( const JsonValue &jv ) const {
2023-
if( jv.test_object() ) {
2024-
JsonObject jo = jv.get_object();
2025-
if( jo.size() != 1 ) {
2026-
jv.throw_error( string_format( "Expected size of 1 for JsonObject, found size %d", jo.size() ) );
2013+
std::pair<T, U> get_next( const JsonValue &jv ) const {
2014+
// elements of the pair to return
2015+
T a;
2016+
U b;
2017+
// [ a, b ]
2018+
if( jv.test_array() ) {
2019+
JsonArray ja = jv.get_array();
2020+
if( ja.size() != 2 ) {
2021+
ja.throw_error( "Must have 2 elements" );
20272022
}
2028-
for( JsonMember jm : jo ) {
2029-
K key( jm.name() );
2030-
V ret;
2031-
jm.read( ret );
2032-
return std::make_pair( key, ret );
2023+
ja[0].read( a, true );
2024+
ja[1].read( b, true );
2025+
return std::make_pair( a, b );
2026+
}
2027+
// { "a": b }
2028+
if constexpr( key_from_json_string<T>::valid ) {
2029+
if( jv.test_object() ) {
2030+
JsonObject jo = jv.get_object();
2031+
if( jo.size() != 1 ) {
2032+
jo.throw_error( "bad object pair, has too many elements" );
2033+
}
2034+
// gross - we need to get the name of the member, this just unwraps it
2035+
for( JsonMember jm : jo ) {
2036+
a = key_from_json_string<T>()( jm.name() );
2037+
jm.read( b, true );
2038+
return std::make_pair( a, b );
2039+
}
20332040
}
20342041
}
2035-
if( !jv.test_array() ) {
2036-
jv.throw_error( "bad pair" );
2037-
}
2038-
JsonArray ja = jv.get_array();
2039-
if( ja.size() != 2 ) {
2040-
ja.throw_error( "Must have 2 elements" );
2042+
// shorthand for a = b
2043+
if constexpr( std::is_same_v<T, U> ) {
2044+
jv.read( a, true );
2045+
b = a;
2046+
return std::make_pair( a, b );
20412047
}
2042-
K l;
2043-
V h;
2044-
ja[0].read( l, true );
2045-
ja[1].read( h, true );
2046-
return std::make_pair( l, h );
2048+
jv.throw_error( "bad pair" );
2049+
return std::make_pair( a, b );
20472050
}
20482051
};
20492052

2053+
// Loads pairs as objects { key1: t1, key2: t2 }, or just t1, which uses the default for the value of t2
2054+
// e.g. named_pair_reader<string, int>( "a", "b", 3 ) loads
2055+
// [ "1", { "a": "3", "b": 4 } ] -> ("1", 3), ("3", 4)
20502056
template<typename T1, typename T2>
20512057
class named_pair_reader : public generic_typed_reader<named_pair_reader<T1, T2>>
20522058
{
@@ -2070,7 +2076,7 @@ class named_pair_reader : public generic_typed_reader<named_pair_reader<T1, T2>>
20702076
jo.read( key2, ret.second, true );
20712077
return ret;
20722078
}
2073-
// TODO: support pair format?
2079+
// TODO: support pair format? Forward to pair_reader<T1, T2>?
20742080
if( jv.test_array() ) {
20752081
jv.throw_error( "invalid format" );
20762082
}

src/item_factory.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3439,7 +3439,7 @@ void islot_seed::deserialize( const JsonObject &jo )
34393439
mandatory( jo, was_loaded, "plant_name", plant_name );
34403440
mandatory( jo, was_loaded, "fruit", fruit_id );
34413441
mandatory( jo, was_loaded, "growth_stages", growth_stages,
3442-
vector_pair_reader<flag_id, time_duration> {} );
3442+
pair_reader<flag_id, time_duration> {} );
34433443
optional( jo, was_loaded, "seeds", spawn_seeds, true );
34443444
optional( jo, was_loaded, "byproducts", byproducts );
34453445
optional( jo, was_loaded, "required_terrain_flag", required_terrain_flag,

src/json.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,38 +51,45 @@ struct is_string_like<T, std::void_t<decltype( std::declval<T &>().substr() )>>
5151
std::true_type { };
5252

5353
template<typename T, typename Enable = void>
54-
struct key_from_json_string;
54+
struct key_from_json_string {
55+
static constexpr bool valid = false;
56+
};
5557

5658
template<>
5759
struct key_from_json_string<std::string, void> {
60+
static constexpr bool valid = true;
5861
std::string operator()( const std::string &s ) {
5962
return s;
6063
}
6164
};
6265

6366
template<typename T>
6467
struct key_from_json_string<string_id<T>, void> {
68+
static constexpr bool valid = true;
6569
string_id<T> operator()( const std::string &s ) {
6670
return string_id<T>( s );
6771
}
6872
};
6973

7074
template<typename T>
7175
struct key_from_json_string<int_id<T>, void> {
76+
static constexpr bool valid = true;
7277
int_id<T> operator()( const std::string &s ) {
7378
return int_id<T>( s );
7479
}
7580
};
7681

7782
template<typename Enum>
7883
struct key_from_json_string<Enum, std::enable_if_t<std::is_enum_v<Enum>>> {
84+
static constexpr bool valid = true;
7985
Enum operator()( const std::string &s ) {
8086
return io::string_to_enum<Enum>( s );
8187
}
8288
};
8389

8490
template<typename T>
8591
struct key_from_json_string<T, std::void_t<decltype( std::declval<T &>().to_string_writable() )>> {
92+
static constexpr bool valid = true;
8693
T operator()( const std::string &s ) {
8794
return T::from_string( s );
8895
}

0 commit comments

Comments
 (0)