5

私の質問は非常に長いです...だから、辛抱してください:)

ExtJS 4 でモデルを扱っていますが、関連付けに問題があるため、モデルの自動作成を実行する関数を作成しました。次の JSON を解析する必要があるとします。

{ 
    "success": true, 
    "total": 28, 
    "itens": [{
        "id":1,
        "nome":"ACRE",
        "sigla":"AC",
        "pais":{
            "id":31,
            "nome":"BRASIL",
            "sigla":"BR"
        }
    },{
        "id":2,
        "nome":"ALAGOAS",
        "sigla":"AL",
        "pais":{
            "id":31,
            "nome":"BRASIL",
            "sigla":"BR"
        }
    }, ...]
}

itens は、Country (ブラジル ポルトガル語の País) を持つ州 (ブラジル ポルトガル語の Estados) を表します。ExtJS アソシエーションを使用しようとしましたが、Java リレーションシップのように動作すると思っていましたが、間違っていました。さて、この JSON には、これらの Java クラスとこれらの Ext モデルがあります (モデルも提供された関数を使用して作成されます)。

Pais.java

@Entity
// named queries here...
public class Pais implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    @NotEmpty
    @Length( max = 100 )
    private String nome;

    @NotNull
    @NotEmpty
    @Column( unique = true )
    @Length( min = 2, max = 4 )
    private String sigla;

    // getters, setters, equals, hashCode and toString here

}

Estado.java

@Entity
// named queries here...
public class Estado implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    @NotEmpty
    @Length( max = 100 )
    private String nome;

    @NotNull
    @NotEmpty
    @Column( unique = true )
    @Length( min = 2, max = 4 )
    private String sigla;

    @NotNull
    @ManyToOne
    private Pais pais;

    // getters, setters, equals, hashCode and toString here

}

モデルを作成する機能

Ext.ns( "Uteis" );

// other utility functions here...

Uteis.createModel = function( modelData ) {

    var fields = modelData.fields;
    var processedFields = [];
    var normalFields = [];
    var relationFields = [];

    for ( var i in fields ) {

        if ( fields[i].type ) {

            switch ( fields[i].type ) {

                case "auto":
                case "string":
                case "int":
                case "float":
                case "boolean":
                case "date":
                    normalFields.push( fields[i] );
                    break;

                default:

                    var relationField = fields[i];

                    var prefix = relationField.name + ".";
                    var modelInstance = Ext.create( relationField.type );

                    modelInstance.fields.each( function( item, index, length ) {

                        var newField = {};

                        // I used this sintax to make possible create only some fields
                        // if I need in the future.
                        newField["name"] = prefix + item.name;
                        newField["type"] = item.type.type;

                        newField["convert"] = item.convert;
                        newField["dateFormat"] = item.dateFormat;
                        newField["defaultValue"] = item.defaultValue;
                        newField["mapping"] = item.mapping;
                        newField["persist"] = item.persist;
                        newField["sortDir"] = item.sortDir;
                        newField["sortType"] = item.sortType;
                        newField["useNull"] = item.useNull;

                        relationFields.push( newField );

                    });

                    break;

            }

        } else {
            normalFields.push( fields[i] );
        }

    }

    processedFields = normalFields.concat( relationFields );

    // debugging code
    /*console.log( "*** " + modelData.name );
    for ( var i in processedFields ) {
        console.log( processedFields[i] );
    }*/

    Ext.define( modelData.name, {
        extend: "Ext.data.Model",
        fields: processedFields
    });

};

関数を使用してモデルを作成する

Uteis.createModel({ 
    name: "Modelos.Pais",
    fields: [
        { name: "id",  type: "int" },
        { name: "nome",  type: "string" },
        { name: "sigla",  type: "string" }
    ]
});

Uteis.createModel({ 
    name: "Modelos.Estado",
    fields: [
        { name: "id",  type: "int" },
        { name: "nome",  type: "string" },
        { name: "sigla",  type: "string" },
        { name: "pais", type: "Modelos.Pais" } // <= references the model created above
    ]
});

上記のコードはこれに対応しています。ネストされたデータ フィールドの作成を自動化する関数を作成しました (前述の関連付けの問題のため)。

Ext.define( "Modelos.Pais", {
    extend: "Ext.data.Model",
    fields: [
        { name: "id",  type: "int" },
        { name: "nome",  type: "string" },
        { name: "sigla",  type: "string" }
    ]
});

Ext.define( "Modelos.Estado", {
    extend: "Ext.data.Model",
    fields: [
        { name: "id",  type: "int" },
        { name: "nome",  type: "string" },
        { name: "sigla",  type: "string" },
        { name: "pais.id",  type: "int" },
        { name: "pais.nome",  type: "string" },
        { name: "pais.sigla",  type: "string" }
    ]
});

OK、これらのモデル (createModel 関数を使用して作成) は、私の JsonStores と非常にうまく連携します。これまで、Java 側のすべてのマッピング アソシエーションは null ではありませんでした。そのため、ストアには常にネストされたデータを処理する必要がありました。ここで、関連付けが null の可能性があるいくつかのエンティティを処理する必要があり、問題が始まりました。このシナリオに対処する必要があるストアが機能しません (フィールドが null であるという例外がストア操作でスローされます)。Gson を使用してエンティティから JSON を作成しています。そのデフォルトの動作は、null フィールドをシリアライズしないことです。それらはクライアント側では定義されません。そのため、null フィールドをシリアライズすると (null を送信すると)、Ext は null フィールドを認識し、それを処理しようとしなくなります。これを行うには、次のコードを使用して Gson を作成しました。

Gson gson = new GsonBuilder().serializeNulls().create();

さて、関連付けが null の JSON が生成されるようになりましたが、Ext は引き続き文句を言います。フィールド マッピングと defaultValue 構成を使用しようとしましたが、成功しませんでした。簡単にするために、Pais が @NotNull ではない Estados と Países (州と国) の例を使用してみましょう。null pais を含む JSON は次のようになります。

{ 
    "success": true, 
    "total": 28, 
    "itens": [{
        "id":1,
        "nome":"ACRE",
        "sigla":"AC",
        "pais":null   // <= here
    },{
        "id":2,
        "nome":"ALAGOAS",
        "sigla":"AL",
        "pais":{  // this is not null
            "id":31,
            "nome":"BRASIL",
            "sigla":"BR"
        }
    }, ...]
}

このコードでは、pais プロパティが null であるため、pais.id、pais.nome、および pais.sigla フィールドは使用できません。だから、私の質問は次のとおりです。nullまたは未定義のフィールドをストアに無視させるにはどうすればよいですか? 私はすでに成功していない解決策を探してみました...どうもありがとうございました!

編集:サーバー側で考えられるいくつかの解決策について一晩中考えた後、過去15分間で解決策を実装しましたが、私は間違いなくそれが好きではありません...使用する前に各オブジェクト「オブジェクトツリー」をトラバースする反射的な方法Gson を使用して、null のフィールドにデフォルト値を設定します。動作していますが、JSON が不必要に大きくなりすぎています。走査方法:

/**
 * A method to traverse the object tree and set "default" values to null fields.
 * 
 * @param target The object to be inspected.
 */
public static void traverseAndSetDefaultValue( Object target ) {

    try {

        for ( Field f : target.getClass().getDeclaredFields() ) {

            // ok to change...
            f.setAccessible( true );

            // is null? so create something
            if ( f.get( target ) == null ) {

                // new instance of the current field
                Object newInstance = null;

                // it needs to traverse to the next level?
                boolean okToTraverse = false;

                switch ( f.getType().getSimpleName() ) {

                    case "Byte":
                    case "Short":
                    case "Integer":
                        newInstance = 0;
                        break;

                    case "Long":
                        newInstance = 0L;
                        break;

                    case "Float":
                        newInstance = 0F;
                        break;

                    case "Double":
                        newInstance = 0D;
                        break;

                    case "Character":
                        newInstance = '\0';
                        break;

                    case "Boolean":
                        newInstance = Boolean.FALSE;
                        break;

                    case "String":
                        newInstance = "";
                        break;

                    case "List":
                        newInstance = new ArrayList();
                        break;

                    case "Set":
                        newInstance = new HashSet();
                        break;

                    default:
                        // calling the default constructor for no 
                        // "default" types
                        newInstance = f.getType().newInstance();
                        okToTraverse = true;
                        break;

                }

                f.set( target, newInstance );

                if ( okToTraverse ) {
                    traverseAndSetDefaultValue( newInstance );
                }

            }

        }

    } catch ( IllegalAccessException | InstantiationException exc ) {
        exc.printStackTrace();
    }

}

それについてどう思いますか... ありがとうございます!

編集 2 : こんにちは。あきらめる!:) 上に投稿したソリューションを使用します。モデルとグリッドとの関係を改善するためのパッチをいくつか見つけました。私はそれらをテストしましたが、null フィールドの問題は続きます (少なくともエラーは消えました)。さて、開発を続ける時が来ました。アプリケーションが完成したら、この問題に戻ってソリューションの改善を試みます。ありがとうございました!

4

2 に答える 2

1

正確なエラーを出していないので、これが問題であるかどうかはわかりません。しかし、以前はオプションのネストされたデータで問題が発生しました。解決策は、モデルにマッピング関数を作成することでした。

Ext.define( "Modelos.Estado", {
    extend: "Ext.data.Model",
    fields: [
        { name: "id",  type: "int" },
        { name: "nome",  type: "string" },
        { name: "sigla",  type: "string" },
        { name: "pais.id",  type: "int", mapping: function( o ) { return o.pais ? o.pais.id : null; } },
        { name: "pais.nome",  type: "string", mapping: function( o ) { return o.pais ? o.pais.nome : null; } },
        { name: "pais.sigla",  type: "string", mapping: function( o ) { return o.pais ? o.pais.sigla : null; } }
    ]
});
于 2012-08-31T19:16:00.083 に答える
1

以下のように Ext.data.reader.Json を拡張できます。

Ext.define('Ext.data.reader.SafeJson', {
    extend: 'Ext.data.reader.Json',
    alias : 'reader.safejson',
    /**
     * @private
     * Returns an accessor function for the given property string. Gives support for properties such as the following:
     * 'someProperty'
     * 'some.property'
     * 'some["property"]'
     * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
     */
    createAccessor: (function() {
        var re = /[\[\.]/;

        return function(expr) {
            if (Ext.isEmpty(expr)) {
                return Ext.emptyFn;
            }
            if (Ext.isFunction(expr)) {
                return expr;
            }
            if (this.useSimpleAccessors !== true) {
                var i = String(expr).search(re);
                if (i >= 0) {
                    if (i > 0) {    // Check all property chain for existence. Return null if any level does not exist.
                        var a = [];
                        var l = expr.split('.');
                        var r = '';
                        for (var w in l) {
                            r = r + '.' + l[w];
                            a.push('obj' + r);
                        }
                        var v = "(" + a.join(" && ") + ") ? obj." + expr + " : null";
                        return Ext.functionFactory('obj', 'return (' + v + ')');
                    } else {
                        return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
                    }
                }
            }
            return function(obj) {
                return obj[expr];
            };
        };
    }()),

        /**
     * @private
     * @method
     * Returns an accessor expression for the passed Field. Gives support for properties such as the following:
     *
     * - 'someProperty'
     * - 'some.property'
     * - 'some["property"]'
     *
     * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
     */
    createFieldAccessExpression: (function() {
        var re = /[\[\.]/;

        return function(field, fieldVarName, dataName) {
            var me     = this,
                hasMap = (field.mapping !== null),
                map    = hasMap ? field.mapping : field.name,
                result,
                operatorSearch;

            if (typeof map === 'function') {
                result = fieldVarName + '.mapping(' + dataName + ', this)';
            } else if (this.useSimpleAccessors === true || ((operatorSearch = String(map).search(re)) < 0)) {
                if (!hasMap || isNaN(map)) {
                    // If we don't provide a mapping, we may have a field name that is numeric
                    map = '"' + map + '"';
                }
                result = dataName + "[" + map + "]";
            } else {                
                if (operatorSearch > 0) {
                    var a = [];
                    var l = map.split('.');
                    var r = '';
                    for (var w in l) {
                        r = r + '.' + l[w];
                        a.push(dataName + r);
                    }
                    result = "("+a.join(" && ")+") ? "+dataName+"."+map+" : null";
                } else {
                    result = dataName + map;
                }
            }            
            return result;
        };
    }())
});

したがって、null ノードを使用してネストされた JSON データを正常に処理できます。

JSON の例:

{
    root: [{
        id: 1,
        name: {
            name: "John",
            phone: "123"
        },          
    },
    {
        id: 4,
        name: null,         
    },
    ]
}

ここで見つけることができるテストデータを使用した作業例: http://jsfiddle.net/8Ftag/

ExtJS 4.1.1 テスト済み

于 2012-10-15T14:39:33.170 に答える