picojson::value に入った picojson::object のネスト構造をドット区切りのパス文字列で引っ張り出すヘルパー

# 概要

[picojson](https://github.com/kazuho/picojson) は C++ で JSON を扱う際に便利なライブラリーだが、JSONの取り扱いにありがちなドット区切りのパス文字列で `picojson::value` にネストして格納された `picojson::object` へ用意にアクセスする機能が実装されていない事にしばしば不便する事がある。 †1

```c++
// 理想: picojson には実装されていないが時折欲しくなる使用例
const auto element_value = root_value.[ "aaa.bbb.ccc" ].as< double >();

// 現実: 毎度こんなに書きたくない
const double element_value
try
{
  element_value =
    root_value.get< picojson::object >().at( "aaa" )
      .get< picojson::object >().at( "bbb" )
        .get< picojson::object >().at( "ccc" )
          .get< double >()
  ;
}
catch( ... )
{ ... }
```

そこで、ネストした `picojson::object` 構造を格納した `picojson::value` からドット区切りのパス文字列で `picojson::value` を引っ張り出すヘルパーライブラリーを作り、使用する。

# 必要な機能

1. ドット区切りのパス文字列を階層ごとに分離する機能: boost::split †1
2. 分離されたパスを取得する機能
3. ユーザーの望む型で返す機能

# 実装詳細

## 基本: パス文字列で引っ張り出す部分

```c++
#include <picojson.h>

#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>

#include <vector>

namespace usagi::json::picojson
{
  using object_type = ::picojson::object;
  using value_type  = ::picojson::value;
  
  /// @brief value_type に対しドット区切りのパスで object_type の階層を辿り value_type を引っ張り出す
  static inline auto get_value( const value_type& source, const std::string& dot_separated_path )
    -> value_type&
  {
    std::vector< std::string > path;
    boost::split( path, dot_separated_path, boost::is_any_of( "." ) );
    
    auto out = const_cast< const value_type* >( &source );
    for ( const auto& path_part : path )
      out = &out->get< object_type >().at( path_part );
    
    return const_cast< value_type& >( *out );
  }
}
```

## おまけ: optional で返す `get_value`

`get_value` はいくつかの例外を発生する可能性がある。

- `picojson::value` を `picojson::object` に `.get` に失敗する場合 -> `std::runtime_error`
- `picojson::object` に `"aaa"` などのキーが存在せず `.at` に失敗する場合 -> `std::out_of_range`

JSONを扱う場合、この様な例外は想定の範囲内の事はままある。そこで、そのような場合を予め想定し、 `boost::optional` で結果を返すヘルパーも用意しておくと便利な事が多い。

```c++
  /// @brief get_value が out_of_range や runtime_error など例外で失敗する場合に optional で例外の送出をカバーする版
  static inline auto get_value_optional( const value_type& source, const std::string& dot_separated_path )
    noexcept
    -> boost::optional< value_type& >
  {
    try
    { return get_value( source, dot_separated_path ); }
    catch ( ... )
    { return { }; }
  }
```

## 応用: 欲しい型で取得できるようにする

picojson が扱う可能性のある型は以下の通り。

- picojson::value の中身の型の可能性
    - `double` : JSON の数値
    - `std::string : JSON の文字列
    - `picojson::array` : JSON の配列
    - `picojson::object` : JSON のオブジェクト（≃辞書）
    - `picojson::null` : JSONの null

C++ で扱いたい事の多い型は以下の通り。

- C++的に受け取りたい事の多い型
    - 数値系
        - `float`, `double`, `long double` : 浮動小数点数型
        - `std::uint8_t`, `std::uint16_t`, `std::uint32_t`, `std::uint64_t` : 非負整数型
        - `std::int8_t`, `std::int16_t`, `std::int32_t`, `std::int64_t` : 整数型
    - `std::string` : 文字列型
    - `std::vector` : 配列
    - `std::unordered_map` : 辞書

特に数値として `double` 以外の型で扱いたい場合に picojson では一旦 `double` で数値を取り出してから `static_cast< int >` などする必要があるのでしばしば面倒。そこで、 `get_value_as< int >` など便利に使えるヘルパーも用意したくなるので、 `picojson::value` -> `T` の取り出しパターンについて考える。

1. `double` -> `double`
2. `double` -> `double` 以外の数値型
3. `string` -> `string`
4. `string` 以外の `picojson::value` の中身の型 -> `string`
5. `picojson::array` -> `picojson::array` （≃ `std::vector` ）
6. `picojson::object` -> `picojson::object` （≃ `std::unordered_map` ）

こんな程度の扱いができると事実上の便利としては困らなくなる。

```c++
  /// @brief get_value + picojson::get + 可能な限りの自動的な型変換（ double や string を float で取り出したり、 null を string で取り出したりもできる ）
  /// @param type_conversion true の場合には可能な限りの自動的な型変換を試みる。 false の場合には value_type::get のみ。
  template < typename T >
  static inline auto get_value_as
  ( const value_type& source
  , const std::string& dot_separated_path
  , const bool type_conversion = true
  ) -> T
  {
    const auto& v = get_value( source, dot_separated_path );
    
    if ( type_conversion )
    {
      // T が数値型の場合の変換込みの処理
      if ( std::is_integral< T >::value or std::is_floating_point< T >::value )
      {
        // double -> T
        if ( v.is< double >() )
          return static_cast< T >( v.get< double >() );
        
        // string -> T
        if ( v.is< std::string >() )
        {
          std::stringstream s;
          s << v.get< std::string >();
          T out;
          s >> out;
          
          if ( s.fail() )
            throw std::runtime_error( "cannot convert to a number type from std::string type." );
          
          return out;
        }
      }
    }
    
    throw std::runtime_error( "cannot convert to a number type from value_type type." );
  }
  
  template < >
  inline auto get_value_as< double >
  ( const value_type& source
  , const std::string& dot_separated_path
  , const bool type_conversion
  ) -> double
  {
    const auto& v = get_value( source, dot_separated_path );
    
    if ( v.is< double >() )
      return v.get< double >();
    
    if ( type_conversion )
    {
      // string -> double
      if ( v.is< std::string >() )
      {
        std::stringstream s;
        s << v.get< std::string >();
        double out;
        s >> out;
        
        if ( s.fail() )
          throw std::runtime_error( "cannot convert to a number type from std::string type." );
        
        return out;
      }
    }
    
    // note: picojson による cast 失敗で適当な std::runtime_error が発行される
    return v.get< double >();
  }
  
  template < >
  inline auto get_value_as< std::string >
  ( const value_type& source
  , const std::string& dot_separated_path
  , const bool type_conversion
  ) -> std::string
  {
    const auto& v = get_value( source, dot_separated_path );
    
    if ( v.is< std::string >() )
      return v.get< std::string >();
    
    if ( type_conversion )
    {
      // value_type の operator<< で string にして返す
      std::stringstream s;
      s << v;
      return s.str();
    }
    
    // note: picojson による cast 失敗で適当な std::runtime_error が発行される
    return v.get< std::string >();
  }
  
  template < >
  inline auto get_value_as< object_type >
  ( const value_type& source
  , const std::string& dot_separated_path
  , const bool type_conversion
  ) -> object_type
  {
    const auto& v = get_value( source, dot_separated_path );
    
    // note: picojson による cast 失敗で適当な std::runtime_error が発行される
    return v.get< object_type >();
  }
  
  template < >
  inline auto get_value_as< array_type >
  ( const value_type& source
  , const std::string& dot_separated_path
  , const bool type_conversion
  ) -> array_type
  {
    const auto& v = get_value( source, dot_separated_path );
    
    // note: picojson による cast 失敗で適当な std::runtime_error が発行される
    return v.get< array_type >();
  }
```

## おまけ: optional で返す `get_value_as`

`get_value` と同様に `get_value_as` にも optional 版があると便利の良い事がままある。

```c++
  /// @brief get_value_as が out_of_range や runtime_error など例外で失敗する場合に optional で例外の送出をカバーする版
  template < typename T >
  static inline auto get_value_as_optional
  ( const value_type& source
  , const std::string& dot_separated_path
  , const bool type_conversion = true
  ) noexcept -> boost::optional< T >
  {
    try
    { return get_value_as< T >( source, dot_separated_path, type_conversion ); }
    catch ( ... )
    { return { }; }
  }
```

# ライブラリー実装例

ライブラリーとしてすぐに使えるように実装を整理すると次のようになる。

- https://github.com/usagi/usagi/blob/master/include/usagi/json/picojson/get_value.hxx

## 使用例

```c++
#include <usagi/json/picojson/get_value.hxx>

#include <iostream>
#include <iomanip>

auto main() -> int
{
  using namespace std;
  
  picojson::value v;
  if ( const auto& e = picojson::parse
    ( v
    , "{ 'aaa':"
      "  { 'bbb':"
      "    { 'ccc': 1.23"
      "    , 'ddd': '345.6789012345678e+9'"
      "  }"
      "}"
    )
  )
    throw runtime_error( e.what() );
  
  cout << v << '\n';
  
  using namespace usagi::json::picojson;
  
  cout << get_value_as< double >( v, "aaa.bbb.ccc" ) << '\n';
  cout << get_value_as< float >( v, "aaa.bbb.ccc" ) << '\n';
  cout << get_value_as< int >( v, "aaa.bbb.ccc" ) << '\n';
  cout << get_value_as< uint16_t >( v, "aaa.bbb.ccc" ) << '\n';
  cout << to_string( get_value_as< uint8_t >( v, "aaa.bbb.ccc" ) ) << '\n';
  cout << get_value_as< string >( v, "aaa.bbb.ccc" ) << '\n';
  cout << get_value_as< picojson::object >( v, "aaa.bbb" ).at( "ccc" ) << '\n';
  cout << setprecision( 10 ) << get_value_as< float >( v, "aaa.bbb.ddd" ) << '\n';
}
```

### 実行結果

```
{"aaa":{"bbb":{"ccc":1.23,"ddd":2.3399999141693115,"eee":"345.6789012345678e+9"}}}
1.23
1.23
1
1
1
1.23
1.23
3.456789053e+011
```

# References

- †1: [kazuho/picojson - Issues - No function to access nested values #44](https://github.com/kazuho/picojson/issues/44)
- †2: [boostjp - 区切り文字を指定して、コンテナを文字列化する](https://boostjp.github.io/tips/string_algo.html#join)