コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

Power Automateでデータ型の判定を頑張って行ってみる


スポンサードリンク

はじめに

わたるふ(@wataruf01)さんのこちらのツイートをみて面白そうだな。と思って頑張ってPower Automateでデータ型の判定を行ってみました。

フローの全体はこんな感じ。

一応サンプル値をいくつか実行してみて、多分動いているだろう。
ということで記事にしてみましたが、このフローが正しいかは正直保証しかねます。

ここおかしくないか?とかありましら、遠慮なく指摘してください。(指摘もらうために公開したまである。)

データ型の判定を行う方法

データ型の判定を行う方法として2種類のフローを考えてみました。

まず1つ目が、元の値とその値を型変換した値を比較して判断する方法
2つ目は、関数が成功したかで型判定する方法
です。

2つめはおいしみ(@ksgiksg )さんもリプで案を上げていましたね。

データ型の判定を行う

今回判定を行う型の一覧は以下です。

  • int型
  • float型
  • DateTime型
  • bool型
  • array型
  • JSON
  • XML
  • string型


2種のフローの共通部は以下になります。

  • 型判定対象の型はアレイ変数に追加する
  • 型判定対象の値は作成アクションにいれる

型判定の結果をarrayの変数にいれているのは、諸事情によるところも正直あるのですが、たとえば以下のような場合はJSON型かつArray型になりますね。

[
    {
        "name": "Bob",
        "age": 20
    },
    {
        "name": "Nancy",
        "age": 18
    }
]

なのでその値の型は配列変数に入れていることにしています。

判定する値を作成アクションに入れている理由は、変数やトリガーのインプットのように明示的にこちらで型宣言をしたくないからです。

これらの部分は2つとも同じなので説明とか省きますね。

元の値とその値を型変換した値を比較して判断する

この方法が私がぱっと思いついた方法ですね。

Tryスコープの中身はこのようになっています。

実行条件ですが、配列変数に追加するアクションが実行されるのはその型判定を行っている作成アクションが成功したときのみ実行しています。

型判定は直前の型判定、つまり配列変数への追加アクションが成功した場合か、スキップされた場合のみ実行しています。
配列変数への追加アクションがスキップされた場合 = 直前の型判定の結果、判定した型ではなかった場合
ですね。

処理をそのまま言葉にするとわかりにくいですが要約すると、型判定はint、string...それぞれ毎回行いますよ。ということです。
失敗したときやタイムアウトしたときに実行しないのは、その場合は不正な結果なのでエラーとして扱いたいからです。

そして型判定を行っている数式ですが、以下のようにしています。

equals(outputs('作成'), int(outputs('作成')))

単純ですね。
判定する値と、判定する値を判定する型に変換した値とで、値が同じかを判定しています。

なぜ同じか?を判定する必要があるかというと、たとえばbool関数って数値や'true'や'false'なんかの文字列を渡しても成功するんですね。

これは公式docsにも実行結果の例として記載されているので、気になる人はみてみてください。

この場合、型判定がエラーになっていないとはいえ、10などの数値をbool値として扱いたくないので、equals関数で値が同じか?を判定しています。

正直この型判定のところではequals関数なくてもいいんですけど、後続の配列変数に追加するアクションと数式を揃えたかったのでこのようにしています。

後からフローをみたときに処理をわかりやすくするためですね。

配列変数に追加するアクションの式は以下のようにしています。

if(
    equals(outputs('作成'), int(outputs('作成'))),
    'int',
    false
)

後の数式は、それぞれの型に変換して判定を行っているだけなので割愛します。
1点、JSONの判定だけが、json関数に渡す値はstringかxmlでないといけないので、元の値をstringに変換してからjson関数に入れています。

equals(outputs('作成'), json(string(outputs('作成'))))

このTryのスコープですが、必ずstringの判定を最後にしてください。

stringの型判定を行っている数式たちは、(多分)エラーにはならないはずです。

equals(outputs('作成'), string(outputs('作成')))
if(
    equals(outputs('作成'), string(outputs('作成'))),
    'string',
    false
)

string関数使ってのstring型への変換は多分ですがエラーにならないんじゃないかな?
もしここがエラーになってしまうと、スコープの最後の処理がエラーになってしまうことになるので、Tryスコープが(成功している処理があっても)エラー扱いになり、後のCatchスコープが問答無用で実行されちゃいます。

stringがエラーになる場合の回避策は次に紹介する方法で記載しています。

CatchスコープはTryスコープが成功しなかったときに実行したいので、以下としています。

中の処理としては単純で配列変数に'Error'を追加しているだけです。

FinallyスコープはCatchスコープが成功かスキップした場合のみ実行しています。

このスコープ内では、型がなんだったかを、作成アクションで表示するようにしています。

ただ、上で述べたように型判定する値が数値だった場合、bool型の判定が失敗せず、配列変数にfalseが追加されてしまっているので、余分なものを取り除いてから型の表示を行います。

これにはアレイのフィルター処理アクションが使えますね。

「項目」というのはitem関数を設定しているとこう表示されます。

数式でみてみるとこうなりますね。

@not(equals(item(), false))

この結果を用いて、作成アクションで値の型を表示させるわけですが、以下の場合はなにかしらエラーが起きているということなので、エラーを表す文言を表示させています。

  • 配列の最後がErrorの場合
  • 配列が空の場合
if(
    and(
        not(equals(last(body('アレイのフィルター処理')), 'Error')),
        not(empty(body('アレイのフィルター処理')))
    ),
    concat('データ型は: ', join(body('アレイのフィルター処理'), ', ')),
    concat(join(body('アレイのフィルター処理'), ''), ' 型判定に失敗しました。')
)

エラーの場合は別にjoin関数いらないんですけど、これでここだけみればCatchが実行されたのか飛ばされたのかが把握できるので入れています。
デバッグ用ですね。

上で共通部はここ!って書きましたが、CatchとFinallyはもう1つのフローでも同じ処理をしています。
上で書かなかった理由はいきなりここの部分だけ書かれてもわからないな。と思ったそれだけの理由です。

この方法のだめな点は、例えば 10 と入力すると、
「データ型は: int, float, json
と返ってくる点です。

正しくは
「データ型は: int」 と返ってきて欲しいですね。

また、DateTime型の判定もDateTimeの定義を年、月、日、時間が含まれている。と定義するならうまくいってないんですよね。

この判定では

yyyy-MM-ddTHH:mm:ss.fffffffK

というタイムフォーマットでないと、DateTime型として認められません。

関数が成功したかで型判定する

初めに書いておくと、この方法はあまりお勧めしません。(私のやり方がよくないだけかもですが)

実行条件の構成は先ほどと同じで、異なるのは設定している式だけです。

この方法では例えばint型の判定ですと、int型をさないと関数の結果としてエラーを返すような関数を用いて、判定を行う方法です。

まずint型の判定ですが、addDays関数を用いています。
もちろん、パラメータとしてint型の値を設定すべき他の関数を用いてもOKです。

int型の判定

addDays(utcNow(), outputs('作成'))

配列変数に追加アクションでは、
このアクションが実行される = 型判定の結果、判定した型であった
ということなのでそのままその型の値をいれています。

float型、json型、xml型はそれぞれの値のみを受け入れるパラメータをもった関数が見つからなかったので、今回対象外にしています。
(これもこの方法がダメな点ですね)
json関数はxmlをパラメータとして受け取りますが、string型も受け入れるので完全な型判定が行えないので、対象外にしています。

他型の判定式を以下列挙していきます。

DateTime型

ticks(outputs('作成'))

bool型

not(outputs('作成'))

array型

join(outputs('作成'), '')

string型

length(outputs('作成'))

このままではstring型でない関数がやってきたとき、stringの判定(最後の判定)でエラーになってしまい、Tryスコープ全体としてエラー扱いになるので、回避策を行う必要があります。

Tryの中でエラーと判定したいときは、配列変数になにも入っていないときですね。
なのでstring型判定が失敗したら、配列変数の中身を確認して、空であればなにもしない(エラーのまま)。値が入っていればなにかアクションを実行する。
ということにします。
(まぁこの方法配列に入っていたのがfalseだけだったらダメなんですけどね!!!)

条件アクションを追加して、実行条件の構成をstring型の配列変数に追加アクションがスキップされたときに設定します。

条件には、配列変数が空でなかった場合。となるようにします。

左辺が

empty(variables('DataType'))

で、右辺が

true

ですね。

はいの場合、つまり配列変数に値があった場合に実行するアクションはなんでもいいのですが、とりあえず失敗しなさそうな、現在の時刻アクションでも入れておきます。

さて、このやり方のダメなところはDateTimeの判定ですね。
DateTime型は(私の理解だと)

yyyy-MM-ddTHH:mm:ss

が基本フォーマットになるはずで、少なくとも

yyyy/MM/dd

はDateTime型ではないはずです。
これはTimeStampを含文字列、string型に分類されるかな。

ただ、Power Automateの関数はそもそもパラメータとしてDateTime型のみを受け入れるものはなく、すべてTimeStampを含む文字列を受け入れる仕様なので、このやり方ではDateTime型ではなくTimeStampを含む文字列の判定しか行えません。

Power Automate側がどう定義しているかちょっとわからないですが(そもそもDateTimeを型として取り扱ってないんじゃないかな)、時間ありをDateTime型、時間なしをDate型と簡単に定義するとしてもこの方法はだめですね。

また、このやり方のダメな点もう1つはjson型やarray型もstring型として判断されてしまう点ですね。

[1, 2, 3] とかを入れて試してみてください。

コピペ用

元の値とその値を型変換した値を比較して判断する

変数を初期化する(DataType)

作成

Try 型判定

Catch 型判定

Finally 型判定

関数が成功したかで型判定する

変数を初期化する(DataType)

作成

Try 型判定

Catch 型判定

Finally 型判定

コピペのやり方

以下記事の「なにこのコードどうやって使うの」でやり方紹介していますので、参考にしてください。

koruneko.hatenablog.com

完成形

両者のやり方にそれぞれ問題点があったので改善してみました。

DateTime型は以下と定義しました。
(ここらへんの定義はよく、DBとかで使われている理解で、それ製品やバージョンによって定義違ったはずだから、公式でこれだよ。っていってくれてないと正直わからんのですよね)

yyyy-MM-ddTHH:mm:ss

もしくはミリ秒があってもOKとします。

yyyy-MM-ddTHH:mm:ss.fffffffK

Date型は

yyyy-MM-dd

単なる日付フォーマットはTimeStampを含む文字列(便宜上TimeStamp型)にします。

新たに判定する型が増えたので、それぞれ判定するアクションを追加します。

まず今まで話してきた日付型たちからいきましょうか。

ちなみに実行条件の構成は変更なしです。

まずはDateTime型ですが、上記のフォーマットであるか?を判断できればよいので、日付部分と時間の部分で分けて判断します。
分けるために、間にある'T'を利用したいと思います。

式にすると以下のようになります。

and(
    equals(
        slice(
            outputs('作成'),
            0,
            mul(sub(length(outputs('作成')), indexOf(outputs('作成'), 'T')), -1)
        ),
        formatDateTime(outputs('作成'), 'yyyy-MM-dd')
    ),
    or(
        equals(
            slice(
                outputs('作成'),
                add(indexOf(outputs('作成'), 'T'), 1)
            ),
            formatDateTime(outputs('作成'), 'HH:mm:ss')
        ),
        equals(
            slice(
                outputs('作成'),
                add(indexOf(outputs('作成'), 'T'), 1)
            ),
            formatDateTime(outputs('作成'), 'HH:mm:ss.fffffffK')
        )    
    )
)

ちょい複雑ですが、上が日付部分の判定、下が時間の判定です。
時間ですが、ミリ秒含むか?も判断必要なので、or関数で秒数含む場合と含まない場合の判断を行っています。

配列変数に追加するアクションは以下のようにしています。

if(
    outputs('型判定(DateTime)'),
    'DateTime',
    false
)

作成アクションの型判定でtrue or falseの判断までしてるんだから、この判定方法でよかったですね。

Date型は以下方法で判断します。

equals(
    outputs('作成'),
    formatDateTime(outputs('作成'), 'yyyy-MM-dd')
)

これは簡単ですね。
もとの値と、フォーマット後の値が一緒か?を判断しているだけです。

配列変数の追加はDateTimeと同じなので割愛。

最後にTimeStamp型ですがこちらは、エラーで判定していたころのDateTime型の判定をTimeStamp型の判定と定義を変えただけです。

残りはfloat型の判定とjson型の判定ですね。

まずfloat型です。

float型は、

  • float関数を実行してもエラーにならない
  • 整数ではなく浮動少数点であればよい

という条件を満たしていればいいですね。

1つ目の条件はただ実行すればいいだけなのでいいとして、問題は2つ目の条件ですね。

値に少数を含むか?は
元の値 - 元の値から小数点以下切り捨てたもの > 0
であればよいです。(作成アクションで 10.00 のように入力しても 10 になるので .00 などは考えないです)

ただし、Power Automateには切り捨てを行う関数は残念ながら存在しないです。
丸めならあるんですけどね。

丸めのやり方はHiroさんがQiitaで纏めてくれている記事があったので、そちらを参照してください。

qiita.com

さて、では切り捨てですが、これはDateTime型判断のときに日付を切り出したとき同様、slice関数使って切り出してあげればいいです。

数式にするとこんな感じですね

greater(
    sub(
        float(outputs('作成')), 
        int(
            slice(
                string(outputs('作成')),
                0,
                mul(
                    sub(
                        length(string(outputs('作成'))), 
                        indexOf(string(outputs('作成')), '.')
                    ), 
                    -1
                )
            )
        )
    ),
    0
)

最後にjson型です。

こいつは、判定条件に最初が「 { 」か最後が「 [ 」であること。
という条件をプラスするだけでOKですね。

厳密な判定をいうと、最後も同じ閉じかっこである必要がありますが、そうでなかった場合はjson関数での変換で引っかかるので今回はそこまでやらなくてOKです。

and(
    or(
        equals(first(string(outputs('作成'))), '{'),
        equals(first(string(outputs('作成'))), '[')
    ),
    equals(outputs('作成'), json(string(outputs('作成'))))
)

これで(多分)型判定がちゃんとできるようになりました。

最後にこの完成後のフローも置いておきます。
変更したのはTryスコープだけなので、Tryスコープだけ記載します。

コピペ用

Try 型判定

さいごに

疲れました。

ブログに残しとこ。と思ったら予想以上に時間かかりました。。。

眠い + 書きながら修正 + 眠い
みたいな状態でかいたので日本語おかしかったり、前後の文おかしいとこあるかもです。。。

そこは指摘してもらえると助かります。

動かなかったりもっといい方法あれば教えてくださいー


スポンサードリンク