Skip to content

Commit 540af4a

Browse files
committed
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 a620bf6 commit 540af4a

File tree

2 files changed

+72
-23
lines changed

2 files changed

+72
-23
lines changed

src/generic_factory.h

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,13 +1908,19 @@ class string_id_reader : public generic_typed_reader<string_id_reader<T>>
19081908

19091909
/**
19101910
* Loads std::pair of [K = string_id, V = int/float] values from JSON -- usually into an std::map
1911+
* It can load either an array, an object, or a single value
19111912
* Accepted formats for elements in an array:
19121913
* 1. A named key/value pair object: "addiction_type": [ { "addiction": "caffeine", "potential": 3 } ]
19131914
* 2. A key/value pair array: "addiction_type": [ [ "caffeine", 3 ] ]
19141915
* 3. A single value: "addiction_type": [ "caffeine" ]
1915-
* A single value can also be provided outside of an array, e.g. "addiction_type": "caffeine"
1916+
* For an object, only the following format is supported
1917+
* 4. "addiction_type": { "caffeine": 3, "alcohol": 2 }
1918+
* A single value can be provided as follows:
1919+
* 5. "addiction_type": "caffeine"
19161920
* For single values, weights are assigned default_weight
19171921
*/
1922+
// TODO: format 1 is quite gross, and just accepts arbitrary keys. The only way it works is by limiting
1923+
// the types of V, which is unfortunate
19181924
template<typename K, typename V>
19191925
class weighted_string_id_reader : public generic_typed_reader<weighted_string_id_reader<K, V>>
19201926
{
@@ -1966,50 +1972,86 @@ class weighted_string_id_reader : public generic_typed_reader<weighted_string_id
19661972
}
19671973
};
19681974

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

1984+
static_assert( key_from_json_string<K>::valid, "Type K must have a known conversion from string" );
1985+
19751986
std::pair<K, V> get_next( const JsonValue &jv ) const {
19761987
const JsonMember *jm = dynamic_cast<const JsonMember *>( &jv );
19771988
if( jm == nullptr ) {
19781989
jv.throw_error( "not part of a JsonObject" );
19791990
}
1980-
K key( jm->name() );
1991+
K key = key_from_json_string<K>()( jm->name() );
19811992
V value;
19821993
jv.read( value, true );
19831994
return std::pair<K, V>( key, value );
19841995
}
19851996
};
19861997

1987-
// Support shorthand for a single value.
1988-
template<typename T>
1989-
class pair_reader : public generic_typed_reader<pair_reader<T>>
1998+
// Supports three formats:
1999+
// 1. [ a, b ]
2000+
// 2. { "a": b }, if there exists a key_from_json_string specialization for a
2001+
// 3. a, if T = U and a is not represented in JSON as an array or object (iff the object format is valid)
2002+
// This would load [ "a", [ "b", "c" ], { "d": "e" } ] into three pairs:
2003+
// ("a", "a"), ("b", "c"), ("d", "e")
2004+
//
2005+
// If you only need format 1, you can also use json_read_reader<std::pair<T, U>>
2006+
//
2007+
// TODO: constructor argument to enable/disable the shorthand format?
2008+
template<typename T, typename U = T>
2009+
class pair_reader : public generic_typed_reader<pair_reader<T, U>>
19902010
{
19912011
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" );
2012+
std::pair<T, U> get_next( const JsonValue &jv ) const {
2013+
// elements of the pair to return
2014+
T a;
2015+
U b;
2016+
// [ a, b ]
2017+
if( jv.test_array() ) {
2018+
JsonArray ja = jv.get_array();
2019+
if( ja.size() != 2 ) {
2020+
ja.throw_error( "Must have 2 elements" );
2021+
}
2022+
ja[0].read( a, true );
2023+
ja[1].read( b, true );
2024+
return std::make_pair( a, b );
2025+
}
2026+
// { "a": b }
2027+
if constexpr( key_from_json_string<T>::valid ) {
2028+
if( jv.test_object() ) {
2029+
JsonObject jo = jv.get_object();
2030+
if( jo.size() != 1 ) {
2031+
jo.throw_error( "bad object pair, has too many elements" );
2032+
}
2033+
// gross - we need to get the name of the member, this just unwraps it
2034+
for( JsonMember jm : jo ) {
2035+
a = key_from_json_string<T>()( jm.name() );
2036+
jm.read( b, true );
2037+
return std::make_pair( a, b );
2038+
}
2039+
}
20002040
}
2001-
JsonArray ja = jv.get_array();
2002-
if( ja.size() != 2 ) {
2003-
ja.throw_error( "Must have 2 elements" );
2041+
// shorthand for a = b
2042+
if constexpr( std::is_same_v<T, U> ) {
2043+
jv.read( a, true );
2044+
b = a;
2045+
return std::make_pair( a, b );
20042046
}
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 );
2047+
jv.throw_error( "bad pair" );
2048+
return std::make_pair( a, b );
20102049
}
20112050
};
20122051

2052+
// Loads pairs as objects { key1: t1, key2: t2 }, or just t1, which uses the default for the value of t2
2053+
// e.g. named_pair_reader<string, int>( "a", "b", 3 ) loads
2054+
// [ "1", { "a": "3", "b": 4 } ] -> ("1", 3), ("3", 4)
20132055
template<typename T1, typename T2>
20142056
class named_pair_reader : public generic_typed_reader<named_pair_reader<T1, T2>>
20152057
{
@@ -2033,7 +2075,7 @@ class named_pair_reader : public generic_typed_reader<named_pair_reader<T1, T2>>
20332075
jo.read( key2, ret.second, true );
20342076
return ret;
20352077
}
2036-
// TODO: support pair format?
2078+
// TODO: support pair format? Forward to pair_reader<T1, T2>?
20372079
if( jv.test_array() ) {
20382080
jv.throw_error( "invalid format" );
20392081
}

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)