Подтвердить что ты не робот

Самый простой способ конвертировать данные json в объекты с приложенными методами?

Какой самый быстрый и простой способ конвертировать мой json, содержащий данные объектов, в реальные объекты с приложенными методами?

В качестве примера я получаю данные для фруктоволокна с множеством фруктовых объектов, которые, в свою очередь, содержат массив семян, таким образом:

{"fruitbowl": [{
     "name": "apple", 
     "color": "red", 
     "seeds": []
   },{
     "name": "orange", 
     "color": "orange", 
     "seeds": [
        {"size":"small","density":"hard"},
        {"size":"small","density":"soft"}
    ]}
}

Что все хорошо и хорошо, но на клиенте мы делаем вещи с этими фруктами, как есть и сажать деревья...

 var fruitbowl = []
 function Fruit(name, color, seeds){
     this.name = name
     this.color = color
     this.seeds = seeds
     this.eat = function(){
         // munch munch
     }
 }
 function Seed(size, density){
     this.size = size
     this.density = density
     this.plant = function(){
          // grow grow
     }
 }

В настоящее время моя процедура успеха ajax в настоящее время зацикливается над вещью и строит каждый объект по очереди, и он еще не обрабатывает семена, потому что, прежде чем я перейду к циклу над конструкторами семян, я думаю

Нет ли лучшего способа?

    success: function(data){           
        fruitbowl.length = 0
        $.each(data.fruitbowl, function(i, f){
            fruitbowl.push(new Fruit(f.name, f.color, f.seeds))
        })

Я не исследовал переходы по объектам так, как они есть, и приложил все методы. Будет ли это работать?

4b9b3361

Ответ 1

Передайте данные конструктору объекта, затем используйте jquery "extend", чтобы объединить данные и методы:

 function Fruit(data){
     $.extend(this, data)
     this.eat = function(){
         // munch munch
     }
 }
 ...
       $.each(data.fruitbowl, function(i, f){
           fruitbowl.push(new Fruit(f))
       })

У вас все еще есть петли; и должен вручную кодировать петли для вложенных объектов (например, семена), но все же очень простой способ преодолеть проблему.

Ответ 2

Да, это сработает, но это нежелательно. Помимо появления слегка взломанной ИМО, вы прилагаете методы к каждому экземпляру ваших фруктов и семян, где вместо этого вы должны использовать цепочку прототипов. Если вы собираетесь использовать instanceof в будущем, этот метод не будет работать.

То, что вы сейчас делаете, - лучшее решение; и вы сможете использовать instanceof.

Если вы чувствуете себя авантюрно, вы можете использовать JSONP вместо AJAX, а ответ JSONP выглядит примерно так:

buildFruitbowl([new Fruit("orange", "blue", [new Seed("small", "hard"), new Seed("big", "soft")]), new Fruit("banana", "yellow", [new Seed("small", "hard"), new Seed("big", "soft")])]);

Это избавит вас от необходимости делать все ваши объекты, и вы получите свои фрукты и семена, как хотите (и instanceof поддержка); однако я все равно буду придерживаться того, что вы делаете уже.

Лучший из ваших растущих бананов.

Ответ 3

Вы можете изменить структуру JSON для хранения информации о типе. Если у вас есть много объектов для сериализации и десериализации взад и вперед, это сэкономит время, набирая собственный код для каждого объекта.

Также обратите внимание, что это изменяет структуру JSON и добавляет свойство __type__ для каждого настраиваемого объекта. Я думаю, что это более чистый подход, чем сохранение отдельных файлов конфигурации. Итак, без дальнейших церемоний, вот как это работает в основном:

var fruitBowl = {..};
fruitBowl[0].eat();
fruitBowl[1].seeds[0].plant();

вызвать сериализацию объекта, чтобы получить представление JSON

var json = fruitBowl.serialize();

десериализуйте вызов в закодированной строке JSON для восстановления объектов

var resurrected = json.deserialize();

теперь вы можете получить доступ к свойствам и методам вызова для объектов:

resurrected[0].eat();
resurrected[1].seeds[0].plant();

Он работает для любых уровней глубоко вложенных объектов, хотя на данный момент это может быть немного ошибкой. Кроме того, это скорее всего не кросс-браузер (проверяется только на Chrome). Поскольку десериализатор не знаком с функцией конструктора объектов, он в основном создает каждый пользовательский объект без передачи каких-либо параметров. Я установил рабочую демонстрацию на jsfiddle в http://jsfiddle.net/kSATj/1/.

Функция конструктора должна была быть изменена для учета двух способов создания объектов:

  • Непосредственно в Javascript
  • Реконструирован из JSON

Все конструкторы должны будут размещать создание с обоих концов, поэтому каждому свойству должно быть присвоено значение резервного значения по умолчанию, если ничего не было передано.

function SomeObject(a, b) {
    this.a = a || false; // defaultValue can be anything
    this.b = b || null; // defaultValue can be anything
}

// one type of initialization that you can use in your code
var o = new SomeObject("hello", "world");

// another type of initialization used by the deserializer
var o = new SomeObject();;
o.a = "hello";
o.b = "world";

Для справки, модифицированный JSON выглядит так:

{"fruitbowl": 
    [
        {
            "__type__": "Fruit",
            "name": "apple",
            "color": "red",
            "seeds": []           
        },
        {
            "__type__": "Fruit",
            "name": "orange",
            "color": "orange",
            "seeds": 
            [
                {
                    "__type__": "Seed",
                    "size": "small",
                    "density": "hard"
                },
                {
                    "__type__": "Seed",
                    "size": "small",
                    "density": "soft"
                }
            ]
        }
    ]
}

Это просто вспомогательная функция для идентификации простых типов:

function isNative(object) {
    if(object == null) {
        return true;
    }

    var natives = [Boolean, Date, Number, String, Object, Function];
    return natives.indexOf(object.constructor) !== -1;
}

Сериализует объект в JSON (с сохраненной информацией о типе):

Object.prototype.serialize = function() {
    var injectTypes = function(object) {
        if(!isNative(object)) {
            object.__type__ = object.constructor.name;
        }

        for(key in object) {
            var property = object[key];
            if(object.hasOwnProperty(key) && !isNative(property)) {
                injectTypes(property);
            }
        }
    };

    var removeTypes = function(object) {
        if(object.__type) {
            delete object.__type__;
        }
        for(key in object) {
            var property = object[key];
            if(object.hasOwnProperty(key) && !isNative(property)) {
                removeTypes(property);
            }
        }
    }

    injectTypes(this);
    var json = JSON.stringify(this);
    removeTypes(this);

    return json;
};

Deserialize (с восстановленными пользовательскими объектами):

String.prototype.deserialize = function() {
    var rawObject = JSON.parse(this.toString());

    var reconstruct = function(object) {
        var reconstructed = {};

        if(object.__type__) {
            reconstructed = new window[object.__type__]();
            delete object.__type__;
        }
        else if(isNative(object)) {
            return object;
        }

        for(key in object) {
            var property = object[key];

            if(object.hasOwnProperty(key)) {
                reconstructed[key] = reconstruct(property);
            }
        }

        return reconstructed;
    }

    return reconstruct(rawObject);
};

Ответ 4

Использование ES5 Object.create

Просто определите свои объекты статически, затем используйте Object.create для их расширения.

Это так же просто, как Object.create(Bowl, transform(data));

// declare 3 Objects to use as prototypes for your data
var Fruit = {
  eat: function() { }
}

var Seed = {
  plant: function() { }
}

var Bowl = {};

// data object
var data = { ... };


// Transform JSON to a valid defineProperties hash.
Object.create(Bowl, transform(data));

Вам нужно будет определить функцию преобразования и, что более важно, указать тип объекта вложенных массивов данных.

// hash map of property names of arrays to the Object they should prototype from.
var collectionClassHash = {
  fruitbowl: Fruit,
  seeds: Seed
}

var transform = function(obj) {
  // return value
  var ret = {};
  // for each key
  Object.keys(obj).forEach(function(key) {
    // value of key
    var temp = obj[key];
    // if array 
    if (Array.isArray(temp) {
      // override value with an array of the correct objects
      temp = obj[key].map(function(val) {
        // recurse for nested objects
        return Object.create(collectionClassHash[key], transform(val));
      });
    } 
    // define getter/setter for value
    ret[key] = {
      get: function() { return temp; },
      set: function(v) { temp = v; }
    }
  });
  return ret;
}

Ответ 5

Используя библиотеку "json2" D Crockford, вы можете предоставить функцию "reviver" для процесса разбора. Функция reviver передается каждой клавишей и каждым значением и должна возвращать фактическое эффективное значение, которое должно использоваться в анализируемом результате.

В методе "stringify" имеется соответствующий необязательный параметр.

Ответ 6

На самом деле мне потребовалось некоторое время, чтобы понять, я действительно удивлен, что на этом не так много страниц.

Как отметил @Pointy, JSON имеет функцию reviver, которая может быть использована для замены встроенного синтаксического анализа, позволяющего избежать ходьбы от дерева во второй раз. Документы на странице JSON возрождают (по моему мнению, немного слабо) - http://json.org/js.html.

Reviver является частью ECMA 5 и поддерживается в Firefox, WebKit (Opera/Chrome) и JSON2.js.

Вот пример кода, основанный на документе JSON. Вы можете видеть, что мы устанавливаем свойство типа на Dog, а затем используем функцию reviver, которая распознает свойство type.

function Dog(args) {
    this.name = args.name;
    this.bark = function() {
        return "bark, bark, my name is " + this.name;
    };
    this.toJSON = function() {
        return {
            name: this.name,
            type: 'Dog'   // this.constructor.name will work in certain browsers/cases
        }
    }
};

var d = new Dog({name:'geti'});

var dAsJson = JSON.stringify(d);
var dFromJson = JSON.parse(dAsJson, function (key, value) {
    var type;
    if (value && typeof value === 'object') {
        type = value.type;
        if (typeof type === 'string' && typeof window[type] === 'function') {
            return new (window[type])(value);
        }
    }
    return value;
}
);

У меня есть пара проблем по поводу их примера. Во-первых, это зависит от того, что конструктор является глобальным (в окне). Во-вторых, проблема безопасности в этом жулике JSON может заставить нас вызвать любой конструктор, добавив свойство type в их JSON.

Я выбрал явный список типов и их конструкторов. Это гарантирует, что только конструкторы, которые, как я знаю, будут безопасными, будут вызваны, а также позволят мне использовать подход сопоставления пользовательского типа, если мне нравится (а не зависит от имени конструктора и его нахождения в глобальном пространстве). Я также проверяю, что объект JSON имеет тип (некоторые могут и не будут обрабатываться нормально).

var jsonReviverTypes = {
    Dog: Dog
};

var dAsJsonB = JSON.stringify(d);
var dFromJsonB = JSON.parse(dAsJsonB, function (key, value) {
    var type;
    if (value && typeof value === 'object' && value.type) {
        type = value.type;
        if (typeof type === 'string' && jsonReviverTypes[type]) {
            return new (jsonReviverTypes[type])(value);
        }
    }
    return value;
});

Примечание. В FF 3.6 есть ошибка в методе JSON.replacer, поскольку @Sky указал и здесь документально подтвержден - http://skysanders.net/subtext/archive/2010/02/24/confirmed-bug-in-firefox-3.6-native-json-implementation.aspx. Для вышеприведенного решения я обойду это, используя toJSON на объекте, а не используя replacer.

Ответ 7

Джон

Надеюсь, не слишком поздно чип здесь. На прошлой неделе у меня была очень похожая проблема, и я решил ее с помощью следующего фрагмента js (его можно было легко преобразовать в jquery).

Здесь базовое использование:

    $(document).ready(function() {
        var bowl = { "fruitbowl": [{
            "name": "apple",
            "color": "red",
            "seeds": []
        },
        {
            "name": "orange",
            "color": "orange",
            "seeds": [
            { "size": "small", "density": "hard" },
            { "size": "small", "density": "soft"}]
        }
        ]
        };

        var serialized = jsonToObject.serialize(bowl);
        var deserialized = jsonToObject.deserialize(serialized);
        // basic tests on serialize/deserializing...
        alert(deserialized.fruitbowl[0].name);
        alert(deserialized.fruitbowl[1].seeds[0].density);
    });

и вот файл jsonToObject.js:

jsonToObject = {

    deserialize: function(_obj) {
        if (typeof (JSON) === 'object' && typeof (JSON.parse) === 'function') {
            // native JSON parsing is available.
            //return JSON.parse(_obj);
        }
        // otherwise, try non-native methods
        var jsonValue = new Function("return " + _obj)();

        if (!jsonValue instanceof Object) {
            jsonValue = eval("(" + _obj + ")");
        }
        return jsonValue;
    },

    serialize: function(_obj) {
        // Let Gecko browsers do this the easy way - not working
        if (_obj != undefined && typeof _obj.toSource !== 'undefined'
            && typeof _obj.callee === 'undefined') {
            return _obj.toSource();
        }

        // Other browsers must do it the hard way
        switch (typeof _obj) {
            // numbers, booleans, and functions are trivial:                 
            // just return the object itself since its default .toString()                 
            // gives us exactly what we want                 
            case 'number':
            case 'boolean':
            case 'function':
                return _obj;
                break;

            // for JSON format, strings need to be wrapped in quotes                 
            case 'string':
                return '"' + _obj.replace(/"/mg, "'") + '"';
                break;

            case 'object':
                var str;
                if (_obj.constructor === Array || typeof _obj.callee !== 'undefined') {
                    str = '[';
                    var i, len = _obj.length;
                    for (i = 0; i < len - 1; i++) { str += this.serialize(_obj[i]) + ','; }
                    str += this.serialize(_obj[i]) + ']';
                }
                else {
                    str = '{';
                    var key;
                    for (key in _obj) { str += key + ':' + this.serialize(_obj[key]) + ','; }
                    str = str.replace(/\,$/, '') + '}';
                }
                return str;
                break;

            default:
                return '""';
                break;
        }
    }
}

надеюсь, что это поможет...

джим

[edit] - вы могли бы, конечно, также дать двум функциям свои прототипные подписи в соответствии с превосходным примером выше, то есть..

String.prototype.deserialize = function() {...} Object.prototype.serialize = function() {...}