web-dev-qa-db-ja.com

各フィールドのifチェックを使用してコードを煩雑にすることなく、多くのJSONフィールドを検証するにはどうすればよいですか?

私はさまざまなフィールドを含むクラスを持っています、そしてそれらは次のようなゲッターとセッターの助けを借りてアクセスできました

public class Student {

    private String name;
    private int age;
    private int sibblingsCount;
    private String elderSibblingName;
    private String youngerSibblingName;

    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.name;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public void setSibblingsCount(int count) {
        this.sibblingsCount = count;
    }
    public int getSibblingsCount() {
        return this.sibblingsCount;
    }

    public void setElderSibblingName(String name) {
        this.elderSibblingName = name;
    }
    public String getElderSibblingName() {
        return this.elderSibblingName;
    }

    public void setYoungerSibblingName(String name) {
        this.youngerSibblingName = name;
    }
    public void getYoungerSibblingName() {
        return this.youngerSibblingName;
    }

    public String getStudentDetails() {    
        JSONObject json = new JSONObject();
        if(name != null && !name.isEmpty()) {
            json.put("name", this.name);
        }

        if(this.age != 0) {
            json.put("age", this.age);
        }

        if(this.sibblingsCount != 0) {
            json.put("sibblingsCount", this.sibblingsCount);
        }

        if(this.elderSibblingName != null && !this.elderSibblingName.isEmpty()) {
            json.put("elderSibblingName", this.elderSibblingName);
        }

        if(this.youngerSibblingName != null && !this.youngerSibblingName.isEmpty() {
            json.put("youngerSibblingName", this.youngerSibblingName);
        }
        return json.toString();
    }
}

私が必要なのは、クラスStudentに有効なフィールドをパックすることだけです。フィールドに値が含まれている場合、そのフィールドは有効であると言います。たとえば、age0ではなく、有効な数値である必要があります。 elderSibblingNameと言ってください。nullまたは空であってはなりません。結果のJSONをパックしながら有効なフィールドを確認するにはどうすればよいですか?

クラス内のフィールドが多すぎる場合、コードが不格好に見えるように、クラスのすべてのファイルに対して有効性をチェックするのは本当に大変です。

2
Tom Taylor

繰り返しのチェックが気になる場合は、それらを関数に抽出して、繰り返しの回数を減らします。例えば:

private static <T> void putIf(JsonObject json, String name, T value, Predicate<T> isValid) {
  if (isValid.test(value)) json.put(name, value);
}

private boolean isNonEmpty(String s) {
  return s != null && !s.isEmpty();
}

private boolean isNonZero(int i) {
  return i != 0;
}

public String getStudentDetails() {    
    JSONObject json = new JSONObject();

    putIf(json, "name", name, x -> isNonEmpty(x));
    putIf(json, "age", age, x -> isNonZero(x));
    putIf(json, "siblingsCount", siblingsCount, x -> isNonZero(x));
    putIf(json, "elderSiblingName", elderSiblingName, x -> isNonEmpty(x));
    putIf(json, "youngerSiblingName", youngerSiblingName, x -> isNonEmpty(x));

    return json.toString();
}

ただし、これはクラスの根本的な問題には対応していません。フィールドに無効な値が含まれている可能性があります。多くの場合、コンストラクタでオブジェクトを完全に初期化し、値も完全に検証しない限りセッターを使用しないことで、これを回避できます。

検証制約がさらに複雑な場合は、特定の検証制約を表す独自のタイプを作成することもできます。例:

final class Name {
  private final String name;

  public Name(String name) {
    if (name == null || name.isEmpty())
      throw ...;
    this.name = name;
  }

  public String get() { return name; }
}

ドメインモデルで値に特定の意味がある場合は、組み込み型やプリミティブ型よりも、このような単純なクラスを優先する必要があります。

これで、コード全体でそのクラスを使用できますが、オブジェクトがnullでないことを確認する必要があります。

public class Student {

    private Name name;
    ...
    private Name elderSibblingName;
    private Name youngerSibblingName;

    public Name getName() {
        return this.name;
    }
    public void setName(Name name) {
        this.name = name;  // assuming null is valid
    }

    ...

    public String getStudentDetails() {    
        JSONObject json = new JSONObject();

        if(name != null) {
            json.put("name", name.get());
        }
        ...
    }
}
2
amon

私の個人的な経験では、検証中に発生する特定の種類の混乱は、コードが通常一時的に結合されているためです。読みにくく、メンテナンスも難しい。宣言型コードのファンなので、検証も宣言型で実装します。

例を示します。次のスキーマがあるとします。

_{
    "payment":{
        "card_number":12345612341234,
        "expires_at":"12/29"
    }
}
_

これを検証する単一の式が欲しいです。そして実際に私はそれを持っています、ここでそれはresult()メソッドに入ります:

_public class ValidatedRegistrationRequest implements Validatable<RegistrationRequest>
{
    private String jsonRequestString;
    private Connection dbConnection;

    public ValidatedRegistrationRequest(String jsonRequestString, Connection dbConnection)
    {
        this.jsonRequestString = jsonRequestString;
        this.dbConnection = dbConnection;
    }

    @Override
    public Result<RegistrationRequest> result() throws Exception
    {
        return
            new FastFail<>(
                new WellFormedJson(
                    new Unnamed<>(Either.right(new Present<>(this.jsonRequestString)))
                ),
                requestJsonObject ->
                    new UnnamedBlocOfNameds<>(
                        List.of(
                            new FastFail<>(
                                new IsJsonObject(
                                    new Required(
                                        new IndexedValue("payment", requestJsonObject)
                                    )
                                ),
                                paymentJsonObject ->
                                    new NamedBlocOfNameds<>(
                                        "payment",
                                        List.of(
                                            new ValidThrueIsNotExpired(
                                                new AsString(
                                                    new Required(
                                                        new IndexedValue("valid_thru", paymentJsonObject)
                                                    )
                                                )
                                            ),
                                            new CardNumberIsNotBlacklisted(
                                                new CardNumberSatisfiesLuhnAlgorithm(
                                                    new Required(
                                                        new IndexedValue("card_number", paymentJsonObject)
                                                    )
                                                ),
                                                this.dbConnection
                                            )
                                        ),
                                        Payment.class
                                    )
                            )
                        ),
                        RegistrationRequest.class
                    )
            )
                .result()
            ;
    }
}
_

残念ながら、ソフトウェアエンジニアリングは行番号を使用してコードをフォーマットできません。 ここ ここで何が起こっているかの行ごとの分析を見つけることができます。

また、ここでは突然変異はありません。シングルセッターではありません。これにより、全体的な保守性が向上します。

このアプローチは、この比較的単純なケースでは一見過剰に見えるかもしれません。しかし、複数のnullチェックと、検証に関連するすべての要点を忘れないでください。とにかく、複雑な検証ルールを持つ複雑なjsonスキーマで使用すると、それは本当に輝きます。

0
Zapadlo