【问题标题】:How to ignore null fields in ExtJS data models?如何忽略 ExtJS 数据模型中的空字段?
【发布时间】:2012-07-12 20:38:49
【问题描述】:

我的问题很长...所以,请耐心等待:)

我正在使用 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 代表拥有国家(巴西葡萄牙语中的 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" }
    ]
});

好的,这些模型(使用我的 createModel 函数创建)与我的 JsonStores 配合得很好。到现在为止,Java 端的所有映射关联都不是空的,所以,我的商店总是有嵌套数据要处理。现在,我必须处理一些可以具有空关联的实体,我的问题开始了。需要处理这种情况的存储不起作用(存储操作中抛出异常,说字段为空)。我正在使用 Gson 从我的实体创建 JSON。它的默认行为是不序列化空字段,它们将在客户端未定义,所以我认为如果我序列化空字段(发送空值)将使 Ext 实现空字段而不尝试处理它。为此,我使用此代码创建了 Gson:

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

好的,现在正在生成带有空关联的 JSON,但 Ext 继续抱怨。我尝试使用字段映射和 defaultValue 配置但没有成功。为了使事情更简单,让我们使用 Estados 和 Países(Privinces 和 Country)的示例,其中 Pais 不再是 @NotNull。带有 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.id、pais.nome 和 pais.sigla 字段将不可用,因为 pais 属性为空。所以,我的问题是:当某些字段为空或未定义时,如何让商店忽略它们?我已经尝试寻找没有成功的解决方案...非常感谢!

编辑:在服务器端考虑了整个晚上的一些可能的解决方案后,我在最后 15 分钟内实现了一个解决方案,但我绝对不喜欢它......它是一种反思方法在使用 Gson 将默认值设置为空字段之前遍历每个对象“对象树”。它正在工作,但是 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:您好。我放弃! :) 我将使用我在上面发布的解决方案。我找到了some patches 来改善与模型和网格的关系。我测试了它们,但空字段的问题仍然存在(至少错误消失了)。好的,现在是继续开发的时候了。应用程序完成后,我将回到这个问题以尝试改进我的解决方案。谢谢!

【问题讨论】:

  • 我不知道模型数据中的空值存在任何具体问题,您能否粘贴您的“Uteis.createModel”函数的输出,以便我们查看模型定义之后的样子它运行了吗?我认为您需要确保在模型上添加带有 Ext 'associations' 属性的嵌套模型数据,但我看不到用于制作模型的函数中是否/在哪里。
  • @dougajmcdonald:嗨。我也试过了,我在帖子里解释了。当您尝试在网格列中获取嵌套值时,关联不适用于 null。该函数的输出在两个代码片段中的代码下方列出。在第一个中,我展示了函数调用,在第二个中,如果我使用 Ext.define() 并扩展 Ext.data.Model,将生成相应的模型。
  • 我认为这是您的问题的解决方案stackoverflow.com/a/12897584/1496088

标签: javascript extjs models extjs4.1


【解决方案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; } }
    ]
});

【讨论】:

    【解决方案2】:

    你可以扩展 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;
            };
        }())
    });
    

    因此,您可以成功处理带有空节点的嵌套 JSON 数据。

    JSON 示例:

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

    您可以在此处找到包含测试数据的工作示例: http://jsfiddle.net/8Ftag/

    ExtJS 4.1.1 测试

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-31
      • 2014-07-10
      • 1970-01-01
      • 1970-01-01
      • 2022-11-23
      • 1970-01-01
      • 1970-01-01
      • 2021-09-02
      相关资源
      最近更新 更多