2014年3月2日日曜日

Data.Aeson+hedisでJSONをいじくって痛風を患った

Haskellのデータ型をJSON化しRedisへ入れ込む


JSONまわりのライブラリにどんなものがあるのか調べつつ、Data.Aesonの出来の素晴らしさに感心しながらサンプルコードを書いてみました。

JSON化したデータをhedis(Redisクライアント)でRedisへ入れ込み、さらにみんなのPHPでHaskellのデータ型をPHPのインスタンスとして復元するという、何とも利用価値がない、コードです。

でこれをですね、実行しますとRedisへJSON化されたデータが格納され、その後格納したデータを再度、haskellのデータ型へ戻しています。

{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}

import Data.Aeson
import Control.Applicative ((<$>),(<*>))
import Control.Monad (mzero)
import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString.Internal as I
import Data.ByteString.Lazy.Char8
import Data.Text.Encoding
import Data.Text
import qualified Data.HashMap.Strict as H
import Database.Redis

-- JSON化する痛風型
data TwoFoose =
  Nyouichi {
    code :: Text,
    name :: Text,
    popularity :: Integer
  } |
  Nyouji {
    code :: Text,
    name :: Text,
    popularity :: Integer
  } |
  Unknown deriving(Show, Eq)

instance FromJSON TwoFoose where
  parseJSON (Object val) = case H.lookup "type" val of
    Just obj -> case obj of
      "Nyouichi" -> Nyouichi <$> val .: "code" <*> val .: "name" <*> val .: "popularity"
      "Nyouji"   -> Nyouji <$> val .: "code" <*> val .: "name" <*> val .: "popularity"
      _          -> return Unknown
  parseJSON _            = mzero

instance ToJSON TwoFoose where
  toJSON (Nyouichi c n p) = object [
                                     "type" .= decodeUtf8 "Nyouichi",
                                     "code" .= c,
                                     "name" .= n,
                                     "popularity" .= p
                                   ]
  toJSON (Nyouji c n p) = object [
                                   "type" .= decodeUtf8 "Nyouji",
                                   "code" .= c,
                                   "name" .= n,
                                   "popularity" .= p
                                 ]

store :: (ToJSON a, RedisCtx m f) => I.ByteString -> a -> m (f Status)
store key v = set key (toStrict $ encode v)

storeObj :: RedisCtx m f => TwoFoose -> m (f Status)
storeObj v = store (encodeUtf8 (code v)) v

loadObj :: RedisCtx m f => I.ByteString -> m (f (Maybe I.ByteString))
loadObj = get

convert :: Either t (Maybe I.ByteString) -> Maybe TwoFoose
convert (Right (Just bs)) = Data.Aeson.decode (fromStrict bs)
convert _ = Nothing

selfConnectInfo :: ConnectInfo
selfConnectInfo = defaultConnectInfo {
    connectHost = "localhost",
    connectPort = PortNumber 6379,
    connectAuth = Nothing,
    connectMaxConnections = 100,
    connectMaxIdleTime = 30
  }

main :: IO()
main = do
  conn <- connect selfConnectInfo
  runRedis conn $ do
    storeObj ichi
    storeObj ji
    i <- loadObj "nyou_kouichi"
    liftIO $ print $ (convert i)
    j <- loadObj "nyou_sanji"
    liftIO $ print $ (convert j)
    u <- loadObj "majika"
    liftIO $ print $ (convert u)
  where
    ichi = Nyouichi "nyou_kouichi" "尿高一" 888888
    ji   = Nyouji "nyou_sanji" "尿惨二" 99999999

もちろんRedisデーモンは起動しておいてください
cuomo@karky7 ~ $ runhaskell RedisJson.hs 
Just (Nyouichi {code = "nyou_kouichi", name = "\23615\39640\19968", popularity = 888888})
Just (Nyouji {code = "nyou_sanji", name = "\23615\24808\20108", popularity = 99999999})
Just Unknown
cuomo@karky7 ~ $ 

Redisへ格納されているJSONの確認

cuomo@karky7 ~ $ redis-cli -h localhost
redis localhost:6379> KEYS *
1) "nyou_kouichi"
2) "majika"
3) "nyou_sanji"
redis localhost:6379> 
redis 127.0.0.1:6379> get nyou_kouichi
"{\"popularity\":888888,\"name\":\"\xe5\xb0\xbf\xe9\xab\x98\xe4\xb8\x80\",\"code\":\"nyou_kouichi\",\"type\":\"Nyouichi\"}"
cuomo@karky7 ~ $ 
尿高一さんが入ってますね

JSONをPHPのインスタンスを作成してみる


Redisへ保存されているJSONから、PHPのオブジェクトインスタンスを作成してみる。おれおれオブジェクト指向なのでPHPでは、ご法度的な技法でも突っ込まないでください 笑...
Redisからすべてのキーを取得して、TwoFooseFactoryでオブジェクトへ変換可能ならインスタンスを作成します。PHPからRedisへアクセスするためPHPのRedisクライアントを入れてください、何でもいいですが、私はこれを入れました。
karky7 ~ # emerge dev-php/pecl-redis

<?php

/**
 * 尿酸ズを生成する工場
 */
class TwoFooseFactory {
  protected $classTable;
  function __construct() {
    $this->classTable = array(
      'Nyouichi' => 1,
      'Nyouji' => 1
    );
  }

  public function createInstance($json) {
    $i = json_decode($json);
    if($i) {
      $instance = NULL;
      $class_name = $this->getClassName($i->type);
      if($class_name != '') {
        $instance = new $class_name($i->code, $i->name, $i->popularity);
      } else {
        $instance = new Unknown();
      }
      return $instance;
    }
    throw new Exception("Json decode error.");
  }

  protected function getClassName($cname) {
    if(array_key_exists($cname, $this->classTable)) {
      return $cname;
    }
    return '';
  }
}

/**
 * PHP5.4.0以降で利用可能
 */
trait SharedFunc {
  protected $code;
  protected $name;
  protected $popularity;

  function __construct($code='', $name='', $popularity='') {
    $this->code = $code;
    $this->name = $name;
    $this->popularity = $popularity;
  }

  public function __get($name) {
    return $this->{$name};
  }
}

/**
 * クラス定義
 */
class Unknown {
  use SharedFunc;
}

class TwoFooseBase {
  use SharedFunc;
}

class Nyouichi extends TwoFooseBase {
}

class Nyouji extends TwoFooseBase {
}

/**
 * main
 */
function main() {
  $redis = new Redis();
  $redis->pconnect('127.0.0.1', 6379);
  $keys = $redis->keys('*');

  $factory = new TwoFooseFactory();

  foreach($keys as $i => $k) {
    $i++;
    $json = $redis->get($k);
    $Instance = $factory->createInstance($json);
    print("= {$i} =\n");
    print("Instance: " . get_class($Instance) . "\n");
    print("    Code: " . $Instance->code . "\n");
    print("    Name: " . $Instance->name . "\n");
    print("     Pop: " . $Instance->popularity . "\n");
    print("\n");
  }
}

main();
?>

PHPを実行して、JSONへアクセスしてみる
cuomo@karky7 ~ $ php RedisJson.php
= 1 =
Instance: Nyouichi
    Code: nyou_kouichi
    Name: 尿高一
     Pop: 888888

= 2 =
Instance: Unknown
    Code: 
    Name: 
     Pop: 

= 3 =
Instance: Nyouji
    Code: nyou_sanji
    Name: 尿惨二
     Pop: 99999999
cuomo@karky7 ~ $ 
ほーぅら、JSONを利用した、Haskellデータ型とPHPのオブジェクトインスタンスの相互利用が可能になりました。

何とか、もう少しでいいのでHaskell力が欲しい今日この頃です
それからお酒の飲みすぎの痛風にはご注意ください。

0 件のコメント:

コメントを投稿