Skip to content

Support for mapping sub maps #34

Description

@jeremylcarter

I notice that the @Mapfield does not support mapping the submaps

export class SubMap {
    name: string
    age: number
}

@Entity()
export class MapItem {
    @ID()
    id: string

    @MapField()
    map: SubMap

    @MapField(SubMap)
    mapList: SubMap[]
}

In this example even if I annotate the SubMap as an Entity it does not map to a SubMap. I tinkered with the code and came up with a function that can recursively map sub entities.

private mapFromField(field: PropertyMetadata, data: any): any {
    const meta = Metadata.entity(field.constructor).get()
    if (!meta) {
      // There is no metadata known for this field type so we will just return it as an anonymous/untyped object.
      return Object.assign(new field.constructor(), data[field.name])
    }

    let entity = new field.constructor()
    const fields = meta.fields

    fields.forEach(field => {
      if (data[field.name] === undefined) {
        return
      } else if (
        (data[field.name]?.toDate !== undefined && typeof data[field.name]?.toDate === 'function') ||
        (typeof data[field.name] === 'object' && data[field.name]?.constructor?.name === 'Timestamp')
      ) {
        entity[field.name] = data[field.name]?.toDate()
      } else if (field?.isDate && data[field.name] && data[field.name]?.length > 0) {
        try {
          entity[field.name] = new Date(data[field.name])
        } catch (error) {
          console.error(error)
        }
      } else if (
        typeof data[field.name] === 'object' &&
        field?.isMap &&
        (typeof field?.constructor === 'object' || typeof field?.constructor === 'function')
      ) {
        if (Array.isArray(data[field.name])) {
          entity[field.name] = data[field.name].map(obj => Object.assign(new field.constructor(), obj))
        } else {
          console.log(field.name)
          entity[field.name] = this.mapField(field, data[field.name])
        }
      } else {
        entity[field.name] = data[field.name]
      }
    })
    return entity
  }

and then the inverse:

private fieldToMap(field: PropertyMetadata, entity: any): any {
    const meta = Metadata.entity(field.constructor).get()
    if (!meta) {
      // There is no meta for this field so just return the destructured value
      return { ...entity }
    }

    const fields = meta.fields
    let dataToSave = {}

    fields.forEach(field => {
      if (field?.isDate && field?.updateOnSave) {
        let date = new Date()
        entity[field.name] = date
        dataToSave[field.name] = date
      } else if (field?.isDate && field?.generateOnCreate && (entity?.__ormOnFire?.isNew || entity.__ormOnFire === undefined)) {
        let date = new Date()
        entity[field.name] = date
        dataToSave[field.name] = date
      } else if (field?.isMap && Array.isArray(entity[field.name])) {
        dataToSave[field.name] = entity[field.name].map(obj => {
          return { ...obj }
        })
      } else if (field?.isMap) {
        dataToSave[field.name] = this.fieldToMap(field, entity[field.name])
      } else {
        dataToSave[field.name] = entity[field.name]
      }
    })

    return dataToSave
  }

Is this something you want me to complete and incorporate upstream?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions