2010-05-14 11 views
17

¿Cuál es la forma más rápida y sencilla de convertir mi json, que contiene los datos de los objetos, en objetos reales con métodos adjuntos?¿La forma más fácil de convertir datos json en objetos con métodos adjuntos?

A modo de ejemplo, puedo recuperar los datos de un frutero con una serie de objetos de frutas, que a su vez contienen una gran variedad de semillas de este modo:

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

Eso es todo bien y bueno, pero abajo en el cliente nos hacer cosas con esta fruta, como comer y plantar árboles ...

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 
    } 
} 

rutina éxito de Mi ajax actualmente está actualmente un bucle sobre la cosa y la construcción de cada objeto a su vez y que no maneja las semillas todavía, porque antes de ir a bucle sobre seed constru ctors Estoy pensando

¿No hay una manera mejor?

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

no he explorado un bucle sobre los objetos como son y unir todos los métodos. Funcionaría eso?

+0

Para crédito adicional: Podría necesitar identificar mi fruta y semillas con "instanceof" en algún momento. –

Respuesta

2

pasar los datos al constructor de objetos a continuación, utilizar jQuery de "extender" para combinar los datos y métodos:

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

Usted todavía tiene bucles involucrados; y debe codificar manualmente los bucles para los objetos anidados (como las semillas), pero sigue siendo una forma muy simple de superar el problema.

2

Sí, funcionaría, pero no es deseable. Además de parecer IMO ligeramente hacky, estás adjuntando métodos a cada instancia de tus frutas y semillas, donde deberías usar la cadena de prototipos. Si va a utilizar instanceof en el futuro, este método no funcionará de todos modos.

Lo que está haciendo actualmente es la mejor solución; y podrás usar instanceof.

Si te sientes aventurero, se puede utilizar en lugar de JSONP AJAX, con la respuesta JSONP buscando algo como:

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")])]); 

que ahorrará tener que hacer todo su objeto bucle, y se le obtenga sus Frutas y Semillas como lo desee (y soporte para instanceof); sin embargo, me apegaría a lo que ya estás haciendo.

Lo mejor de lo mejor es cultivar sus plátanos.

+2

"Lo mejor de ti cultivando tus plátanos", nunca había escuchado eso antes. – Anurag

1

Utilizando la biblioteca "json2" de D Crockford, puede suministrar una función "reviver" al proceso de análisis sintáctico. La función reviver se pasa cada clave y cada valor, y debe devolver el valor efectivo real que se utilizará en el resultado analizado.

Hay un parámetro opcional correspondiente en el método "stringify".

+1

advertencia: siempre que decida utilizar el reviver necesita forzar una sobreescritura de mozilla/firefox <3.6 JSON nativo con json2.js. Hay una falla documentada en la implementación de reviver. –

2

Puede modificar la estructura JSON para almacenar la información de tipo. Si tiene muchos objetos para serializar y deserializar de ida y vuelta, esto ahorraría tiempo escribiendo código personalizado para cada objeto.

También tenga en cuenta que esto modifica la estructura JSON y agrega una propiedad __type__ a cada objeto personalizado. Creo que este es un enfoque más limpio que mantener archivos de configuración separados. Así que sin más preámbulos, esta es la forma en que funciona básicamente:

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

llamada serializar en el objeto de conseguir una representación JSON

var json = fruitBowl.serialize(); 

deserializar llamada en la cadena JSON codificado para reconstruir los objetos

var resurrected = json.deserialize(); 

ahora se puede acceder a las propiedades y métodos de compra sobre los objetos:

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

Funciona para cualquier nivel de objetos profundamente anidados, aunque podría ser un poco problemático por el momento. También es probable que no sea un navegador cruzado (solo probado en Chrome). Como el deserializador no está familiarizado con la función de constructor de un objeto, básicamente crea cada objeto personalizado sin pasar ningún parámetro. Configuré una demostración en funcionamiento en jsfiddle al http://jsfiddle.net/kSATj/1/.

La función constructora tuvo que ser modificado para tener en cuenta los dos aspectos es objetos se podrían crear

  1. directamente en Javascript
  2. reconstruido a partir de JSON

Todos los constructores necesitarían para dar cabida a la creación desde ambos extremos, por lo que cada propiedad debe tener asignado un valor de retorno predeterminado en caso de que no se pase nada.

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"; 

Como referencia, el JSON modificado parece:

{"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" 
       } 
      ] 
     } 
    ] 
} 

Esto es sólo una función de ayuda para identificar los tipos simples:

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

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

serializa un objeto en JSON (con información de tipo preservado):

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 (con objetos personalizados reconstituidas)

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); 
}; 
+0

¡Guau, sobresalientes por el esfuerzo! No muchos por ser más fácil. La idea de pasar __type__ se anota. –

+0

Estoy de acuerdo, esto no es muy útil si tiene pocos objetos. Construir objetos a mano desde JSON es definitivamente un enfoque mejor y más fácil en ese caso.Creo que ya he mencionado los inconvenientes de mi solución - * buggy *, * no cross-browser *, y * contamina JSON *, pero con un poco más de esfuerzo, este enfoque debería empezar a dar sus frutos ya que la cantidad de objetos que se necesitan ser reconstruido sube. – Anurag

0

John,

Esperemos que no sea demasiado tarde para chip de aquí. Tuve un problema muy similar la semana pasada y lo resolví con la siguiente pieza de js (también podría convertirse fácilmente en jquery).

Aquí está el uso de la base:

$(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); 
    }); 

y aquí está el archivo jsonToObject.js:

esperanza esto ayuda ...

Jim

[editar] - se puede dar por supuesto también las dos funciones de sus firmas prototipo de acuerdo con la excelente ejemplo anterior, es decir ..

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

+0

gracias por la idea. la deserialización siempre se realiza en cadenas, por lo que idealmente debería agregarse al prototipo de cadena. – Anurag

+0

sí, hice una versión local que usó el prototipo sigs, pero dejé mi ejemplo dado que el suyo toma este enfoque - da un poco de variedad :) por cierto - todavía necesita actualizar el signiture a cadena en su ejemplo (http://jsfiddle.net/kSATj/) - por si acaso lo pasa por alto;) –

+0

La variedad siempre es buena :) y gracias por el consejo de cuerdas. Acabo de actualizar mi ejemplo – Anurag

1

Esto en realidad me tomó un tiempo darme cuenta, estoy realmente sorprendido de que no haya más páginas sobre esto.

Como señaló @Pointy, JSON tiene una función de reactivación que se puede utilizar para reemplazar el resultado del análisis en línea, lo que le permite evitar caminar por el árbol una segunda vez. La página JSON documenta el restablecimiento (en mi opinión un poco débil) - http://json.org/js.html.

Reviver es parte de ECMA 5 y es compatible con Firefox, WebKit (Opera/Chrome) y JSON2.js.

Aquí hay un ejemplo de código basado en el documento JSON. Puede ver que estamos configurando una propiedad type en Dog y luego usando una función reviver que reconoce esa propiedad 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; 
} 
); 

Tengo un par de preocupaciones sobre su ejemplo. La primera es que depende de que el constructor sea global (en la ventana). El segundo es un problema de seguridad en ese JOG rogue que puede hacer que llamemos a cualquier constructor agregando una propiedad de tipo a su JSON.

He elegido tener una lista explícita de tipos y sus constructores. Esto asegura que solo los constructores que conozco como seguros serán llamados y también me permite usar un enfoque de mapeo de tipos personalizado si me gusta (en lugar de depender del nombre del constructor y del espacio global). También verifico que el objeto JSON tiene un tipo (algunos pueden no y se tratarán normalmente).

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; 
}); 

Nota, FF 3.6 tiene un error en el método JSON.replacer como @Sky señaló y ha documentado aquí - http://skysanders.net/subtext/archive/2010/02/24/confirmed-bug-in-firefox-3.6-native-json-implementation.aspx. Para la solución anterior, trabajo alrededor de esto usando TOJSON en el objeto en lugar de usar replacer.

2

Usando ES5 Object.create

Basta con definir sus objetos estáticamente a continuación, utilizar Object.create para extenderlos.

Es tan simple como 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)); 

Usted tendrá que definir la función de transformación y lo más importante decir que el tipo de objeto de matrices anidadas de datos.

// 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; 
} 
Cuestiones relacionadas