ti-enxame.com

Como adicionar métodos ao protótipo de um objeto (JSON)?

Digamos que eu receba algum objeto JSON do meu servidor, por exemplo, alguns dados para um objeto Person:

{firstName: "Bjarne", lastName: "Fisk"}

Agora, quero alguns métodos em cima desses dados, por exemplo para calcular o fullName:

fullName: function() { return this.firstName + " " + this.lastName; }

De maneira que eu possa

var personData = {firstName: "Bjarne", lastName: "Fisk"};
var person = PROFIT(personData);
person.fullName(); // => "Bjarne Fisk"

O que eu basicamente gostaria de fazer aqui é adicionar um método ao protótipo do objeto. O método fullName() é geral, portanto não deve ser adicionado ao próprio objeto de dados. Gostar..:

personData.fullName = function() { return this.firstName + " " + this.lastName; }

... causaria muita redundância; e indiscutivelmente "poluir" o objeto de dados.

Qual é a melhor maneira atual de adicionar esses métodos a um objeto de dados simples?

EDITAR:

Ligeiramente fora do tópico, mas se o problema acima pode ser resolvido, seria possível fazer um bom pseudo -pattern matching assim:

if ( p = Person(data) ) {
   console.log(p.fullName());
} else if ( d = Dog(data) ) {
   console.log("I'm a dog lol. Hear me bark: "+d.bark());
} else {
   throw new Exception("Shitty object");
}

Person e Dog adicionarão os métodos se o objeto data tiver os atributos corretos. Se não, devolva falsamente (ou seja, os dados fazem não correspondência/conformidade).

PERGUNTA DE BÔNUS: Alguém sabe de uma biblioteca que usa ou permite isso (ou seja, torna mais fácil)? Já é um padrão de javascript? Se sim, como é chamado? e você tem um link que elabora? Obrigado :)

28
kornfridge

Supondo que seu Objeto venha de alguma biblioteca JSON que analise a saída do servidor para gerar um Objeto, em geral ele não terá nada específico em seu protótipo; e dois objetos gerados para diferentes respostas do servidor não compartilharão uma cadeia de protótipos (além do Object.prototype, é claro;)) 

Se você controlar todos os lugares onde uma "Person" é criada a partir do JSON, você poderia fazer as coisas ao contrário: criar um objeto Person "vazio" (com um método como fullName em seu protótipo) e estendê-lo com o objeto gerado do JSON (usando $ .extend, _.extend ou algo similar). 

var p = { first : "John", last : "Doe"};

function Person(data) {
   _.extend(this, data);
}

Person.prototype.fullName = function() {
   return this.first + " " + this.last;   
}

console.debug(new Person(p).fullName());
20
phtrivier

Existe outra possibilidade aqui. JSON.parse aceita um segundo parâmetro, que é uma função usada para reviver os objetos encontrados, dos nós folha para o nó raiz. Então, se você pode reconhecer seus tipos com base em suas propriedades intrínsecas, você pode construí-los em uma função reviver. Aqui está um exemplo muito simples de fazer isso:

var MultiReviver = function(types) {
    // todo: error checking: types must be an array, and each element
    //       must have appropriate `test` and `deserialize` functions
    return function(key, value) {
        var type;
        for (var i = 0; i < types.length; i++) {
            type = types[i];
            if (type.test(value)) {
                return type.deserialize(value);
            }
        }
        return value;
    };
};

var Person = function(first, last) {
    this.firstName = first;
    this.lastName = last;
};
Person.prototype.fullName = function() {
    return this.firstName + " " + this.lastName;
};
Person.prototype.toString = function() {return "Person: " + this.fullName();};
Person.test = function(value) {
    return typeof value.firstName == "string" && 
           typeof value.lastName == "string";
};
Person.deserialize = function(obj) {
    return new Person(obj.firstName, obj.lastName);
};

var Dog = function(breed, name) {
    this.breed = breed;
    this.name = name;
}
Dog.prototype.species = "canine";
Dog.prototype.toString = function() {
    return this.breed + " named " + this.name;
};
Dog.test = function(value) {return value.species === "canine";};
Dog.deserialize = function(obj) {return new Dog(obj.breed, obj.name);};


var reviver = new MultiReviver([Person, Dog]);

var text = '[{"firstName": "John", "lastName": "Doe"},' +
            '{"firstName": "Jane", "lastName": "Doe"},' +
            '{"firstName": "Junior", "lastName": "Doe"},' +
            '{"species": "canine", "breed": "Poodle", "name": "Puzzle"},' +
            '{"species": "canine", "breed": "Wolfhound", "name": "BJ"}]';

var family = JSON.parse(text, reviver)
family.join("\n");

// Person: John Doe
// Person: Jane Doe
// Person: Junior Doe
// Poodle named Puzzle
// Wolfhound named BJ

Isso depende de você ser capaz de reconhecer inequivocamente seus tipos. Por exemplo, se houvesse algum outro tipo, até mesmo um subtipo de Person, que também tivesse propriedades firstName e lastName, isso não funcionaria. Mas isso pode cobrir algumas necessidades.

9
Scott Sauyet

Se você está lidando com dados JSON simples, o protótipo de cada objeto pessoa seria simplesmente Object.prototype. Para transformá-lo em um objeto com um protótipo de Person.prototype você primeiro precisa de um construtor e protótipo Person (assumindo que você está fazendo Javascript OOP da maneira tradicional):

function Person() {
    this.firstName = null;
    this.lastName = null;
}
Person.prototype.fullName = function() { return this.firstName + " " + this.lastName; }

Então, você precisaria de uma maneira de transformar um objeto simples em um objeto Person, por exemplo, se você tivesse uma função chamada mixin que simplesmente copiava todas as propriedades de um objeto para outro, você poderia fazer isso:

//example JSON object
var jsonPerson = {firstName: "Bjarne", lastName: "Fisk"};

var person = new Person();
mixin(person, jsonPerson);

Esta é apenas uma maneira de resolver o problema, mas esperamos que você tenha algumas idéias.


Atualização: Agora que Object.assign() está disponível em navegadores modernos, você pode usar isso em vez de escrever sua própria função mixin. Há também um shim para fazer Object.assign() funcionar em navegadores mais antigos; veja https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill .

5
Matt Browne

Você provavelmente não deveria fazer isso.

JSON permite serializar um estado, não um tipo. Então, no seu caso de uso, você deve fazer algo assim:

var Person = function ( data ) {
    if ( data ) {
        this.firstName = data.firstName;
        this.lastName = data.lastName;
    }
};

Person.prototype.fullName = function ( ) {
    return this.firstName + ' ' + this.lastName;
};

//

var input = '{"firstName":"john", "lastName":"Doe"}';
var myData = JSON.parse( input );
var person = new Person( myData );
4
Maël Nison

Em outras palavras, você quer mudar o protótipo (classe a.k.a) do objeto existente. Tecnicamente, você pode fazer da seguinte maneira:

var Person = {
  function fullName() { return this.firstName + " " + this.lastName; }
};

// that is your PROFIT function body: 
personData.__proto__ = Person ;

Depois disso, se você vai obter true em personData instanceof Person

3
c-smile

Use o new-ish Object.setPrototypeOf () . (É suportado pelo IE11 e todos os outros navegadores agora.)

Você poderia criar uma classe/protótipo que incluísse os métodos desejados, como seu fullName () e, em seguida,

Object.setPrototypeOf( personData, Person.prototype );

Como o aviso (na página MDN link acima) sugere, esta função não deve ser usada levemente, mas isso faz sentido quando você está mudando o protótipo de um objeto existente, e é isso que você parece ser depois.

1
Tom

Você não precisa usar protótipos para vincular um método personalizado ao seu objeto barebone.

Aqui você tem um exemplo elegante que não polui seu código evitando código redundante 

var myobj = {
  title: 'example',
  assets: 
  {
    resources: ['zero', 'one', 'two']
  }
}

var myfunc = function(index)
{
    console.log(this.resources[index]); 
}

myobj.assets.giveme = myfunc

myobj.assets.giveme(1);

Exemplo disponível em https://jsfiddle.net/bmde6L0r/

0
Juan Lago

Não acho que seja comum transportar métodos com dados, mas parece uma ótima ideia.

Este projeto permite que você codifique as funções junto com seus dados, mas não é considerado padrão, e requer decodificação com a mesma biblioteca, é claro.

https://github.com/josipk/json-plus
0
Billy Moon

Objetos anônimos não possuem um protótipo. Por que não apenas ter isso:

function fullName(obj) {
    return obj.firstName + ' ' + obj.lastName;
}

fullName(person);

Se você absolutamente deve usar uma chamada de método em vez de uma chamada de função, você sempre pode fazer algo semelhante, mas com um objeto.

var Person = function (person) { this.person = person; }
Person.prototype.fullName = function () {
    return this.person.firstName + ' ' + this.person.lastName;
}
var person = new Person(personData);
person.fullName();
0
Explosion Pills