npm i merge-anything
Mesclar objetos e outros tipos recursivamente. Totalmente compatível com TypeScript ! Uma integração simples e pequena.
Eu criei este pacote porque tentei muitos pacotes semelhantes que fazem fusão/deepmerging/atribuição de objetos recursivos, etc. Mas todos tinham suas peculiaridades, e todos eles quebram coisas que não deveriam quebrar ...?
Eu estava procurando:
Object.assign()
mas profunda Este último é crucial! Em JavaScript quase tudo é um object , claro, mas não quero uma função de mesclagem tentando mesclar, por exemplo. duas new Date()
! Muitas bibliotecas usam classes personalizadas que criam objetos com protótipos especiais, e todos esses objetos quebram ao tentar mesclá-los. Então temos que ter cuidado!
merge-anything irá mesclar objetos e propriedades aninhadas, mas apenas enquanto forem "objetos simples". Assim que um sub-prop não for um "objeto simples" e tiver um protótipo especial, ele copiará essa instância "como está". ♻️
import { merge } from 'merge-anything'
const starter = { name : 'Squirtle' , types : { water : true } }
const newValues = { name : 'Wartortle' , types : { fighting : true } , level : 16 }
const evolution = merge ( starter , newValues , { is : 'cool' } )
// returns {
// name: 'Wartortle',
// types: { water: true, fighting: true },
// level: 16,
// is: 'cool'
// }
No exemplo acima, se você estiver usando TypeScript e passar o mouse sobre evolution
, poderá ver o tipo do seu novo objeto ali mesmo. Isso é muito poderoso, porque você pode mesclar coisas e, sem precisar any
, o TypeScript saberá exatamente a aparência dos objetos recém-mesclados!
O tipo de retorno da função merge()
também pode ser usado como um utilitário TypeScript:
import type { Merge } from 'merge-anything'
type A1 = { name : string }
type A2 = { types : { water : boolean } }
type A3 = { types : { fighting : boolean } }
type Result = Merge < A1 , [ A2 , A3 ] >
Este pacote passará recursivamente por objetos simples e mesclará os valores em um novo objeto.
Observe que este pacote reconhece objetos JavaScript especiais, como instâncias de classe. Nesses casos, ele não irá mesclá-los recursivamente como objetos, mas atribuirá a classe ao novo objeto "como está"!
// all passed objects do not get modified
const a = { a : 'a' }
const b = { b : 'b' }
const c = { c : 'c' }
const result = merge ( a , b , c )
// a === {a: 'a'}
// b === {b: 'b'}
// c === {c: 'c'}
// result === {a: 'a', b: 'b', c: 'c'}
// However, be careful with JavaScript object references with nested props. See below: A note on JavaScript object references
// arrays get overwritten
// (for "concat" logic, see Extensions below)
merge ( { array : [ 'a' ] } , { array : [ 'b' ] } ) // returns {array: ['b']}
// empty objects merge into objects
merge ( { obj : { prop : 'a' } } , { obj : { } } ) // returns {obj: {prop: 'a'}}
// but non-objects overwrite objects
merge ( { obj : { prop : 'a' } } , { obj : null } ) // returns {obj: null}
// and empty objects overwrite non-objects
merge ( { prop : 'a' } , { prop : { } } ) // returns {prop: {}}
merge-anything mantém objetos especiais intactos, como datas, regex, funções, instâncias de classe, etc.
No entanto, é muito importante que você entenda como contornar referências de objetos JavaScript. Certifique-se de ler #a nota sobre referências de objetos JavaScript abaixo.
O comportamento padrão é que as matrizes sejam substituídas. Você pode importar mergeAndConcat
se precisar concatenar matrizes. Mas não se preocupe se você não precisar disso, esta biblioteca pode ser abalada e não importará código que você não usa!
import { mergeAndConcat } from 'merge-anything'
mergeAndConcat (
{ nested : { prop : { array : [ 'a' ] } } } ,
{ nested : { prop : { array : [ 'b' ] } } }
)
// returns { nested: { prop: { array: ['a', 'b'] } } },
Pode haver momentos em que você precise ajustar a lógica quando duas coisas são mescladas. Você pode fornecer sua própria função personalizada que é acionada sempre que um valor é substituído.
Para este caso usamos mergeAndCompare
. Aqui está um exemplo com uma função de comparação que concatena strings:
import { mergeAndCompare } from 'merge-anything'
function concatStrings ( originVal , newVal , key ) {
if ( typeof originVal === 'string' && typeof newVal === 'string' ) {
// concat logic
return ` ${ originVal } ${ newVal } `
}
// always return newVal as fallback!!
return newVal
}
mergeAndCompare ( concatStrings , { name : 'John' } , { name : 'Simth' } )
// returns { name: 'JohnSmith' }
Nota para usuários TypeScript. O tipo retornado por esta função pode não estar correto. Nesse caso, você deve transmitir o resultado para sua própria interface fornecida
Tenha cuidado com a referência de objetos JavaScript. Qualquer propriedade aninhada será reativa e vinculada entre o objeto original e os objetos mesclados! Abaixo mostraremos como evitar isso.
const original = { airport : { status : 'dep. ?' } }
const extraInfo = { airport : { location : 'Brussels' } }
const merged = merge ( original , extraInfo )
// we change the status from departuring ? to landing ?
merged . airport . status = 'lan. ?'
// the `merged` value will be modified
// merged.airport.status === 'lan. ?'
// However `original` value will also be modified!!
// original.airport.status === 'lan. ?'
A regra principal a lembrar é:
Qualquer propriedade aninhada em mais de um nível sem uma propriedade pai sobreposta será reativa e vinculada tanto no resultado da mesclagem quanto na origem
No entanto, existe uma solução realmente fácil . Podemos simplesmente copiar o resultado da mesclagem para nos livrar de qualquer reatividade. Para isso podemos usar a biblioteca copy-anything. Esta biblioteca também garante que instâncias de classes especiais não sejam quebradas , para que você possa usá-la sem medo de quebrar coisas!
Veja abaixo como integramos 'copy-anything':
import { copy } from 'copy-anything'
const original = { airport : { status : 'dep. ?' } }
const extraInfo = { airport : { location : 'Brussels' } }
const merged = copy ( merge ( original , extraInfo ) )
// we change the status from departuring ? to landing ?
merged . airport . status = 'lan. ?' ( merged . airport . status === 'lan. ?' ) (
// true
// `original` won't be modified!
original . airport . status === 'dep. ?'
) // true
Você pode então brincar onde deseja colocar a função copy()
.
Copy Anything também é totalmente compatível com TypeScript!
É literalmente apenas passar por um objeto recursivamente e atribuir os valores a um novo objeto como abaixo. No entanto, ele está empacotado para permitir parâmetros extras, etc. O código abaixo é a integração básica, que fará você entender o básico de como funciona.
import { isPlainObject } from 'is-what'
function mergeRecursively ( origin , newComer ) {
if ( ! isPlainObject ( newComer ) ) return newComer
// define newObject to merge all values upon
const newObject = isPlainObject ( origin )
? Object . keys ( origin ) . reduce ( ( carry , key ) => {
const targetVal = origin [ key ]
if ( ! Object . keys ( newComer ) . includes ( key ) ) carry [ key ] = targetVal
return carry
} , { } )
: { }
return Object . keys ( newComer ) . reduce ( ( carry , key ) => {
const newVal = newComer [ key ]
const targetVal = origin [ key ]
// early return when targetVal === undefined
if ( targetVal === undefined ) {
carry [ key ] = newVal
return carry
}
// When newVal is an object do the merge recursively
if ( isPlainObject ( newVal ) ) {
carry [ key ] = mergeRecursively ( targetVal , newVal )
return carry
}
// all the rest
carry [ key ] = newVal
return carry
} , newObject )
}
* Claro, existem pequenas diferenças com o código-fonte real para lidar com casos raros e recursos extras. O código-fonte real está aqui.