mortillan
mortillan

Reputation: 41

Mongoose Subdocument for Geojson

I am not able to define a correct reusable Point Schema. I just copied the example schema in https://mongoosejs.com/docs/geojson.html

This is the error I'm encountering when starting the node.js app

/home/******/projects/realista-api/node_modules/mongoose/lib/schema.js:418 throw new TypeError('Invalid value for schema path ' + prefix + key + ''); ^ TypeError: Invalid value for schema path coordinates

I already tried using a non-reusable schema. By directly defining it at the parent schema and it works

coordinates: {
    type: {
      type: String,
      enum: ['Point'],
      required: true
    },
    coordinates: {
      type: [Number],
      required: true
    }
  },

Here is the code

import { Schema, Document } from 'mongoose';

interface Point extends Document {
  type: string,
  coordinates: Array<number>,
}

const PointSchema: Schema = new Schema({
  type: {
    type: String,
    enum: ['Point'],
    required: true
  },
  coordinates: {
    type: [Number],
    required: true
  }
}, {
  id: false
});

export {
  Point,
  PointSchema,
}

I'm using that as subdocument in another schema

const ProjectSchema: Schema = new Schema({
  owner: {
    type: Schema.Types.ObjectId,
    ref: 'User',
    required: false,
  },
  logo: {
    type: String,
    required: false,
  },
  name: {
    type: String,
    required: true,
  },
  location: {
    type: String,
    required: false,
  },
  suburb: {
    type: String,
    required: false,
  },
  stateCode: {
    type: String,
    required: false,
  },
  country: {
    type: String,
    required: false,
  },
  countryName: {
    type: String,
    required: false,
    unique: true,
    sparse: true,
  },
  coordinates: PointSchema,// this is the field in question
  commission: {
    type: Schema.Types.Decimal128,
    required: false,
  },
  tax: {
    type: Schema.Types.Decimal128,
    required: false,
  },
  propertyType: {
    type: Schema.Types.ObjectId,
    ref: 'PropertyType',
    required: true,
  },
  address: {
    type: String,
    required: false,
  },
  title: {
    type: String,
    required: false,
  },
  description: {
    type: String,
    required: false,
  },
  videoTour: {
    type: String,
    required: false,
  },
  matterPortUrl: {
    type: String,
    required: false,
  },
  currency: {
    type: String,
    required: false,
  },
  minPrice: {
    type: Schema.Types.Decimal128,
    required: false,
  },
  maxPrice: {
    type: Schema.Types.Decimal128,
    required: false,
  },
  otherPrice: {
    type: String,
    required: false,
  },
  featureLandSizeMin: {
    type: String,
    required: false,
  },
  featureLandSizeMax: {
    type: String,
    required: false,
  },
  featureLandSizeUnit: {
    type: String,
    required: false,
  },
  featureBuiltStart: {
    type: Date,
    required: false,
  },
  featureBuiltEnd: {
    type: Date,
    required: false,
  },
  featureNumOfLevel: {
    type: Number,
    required: false,
  },
  featureNumOfUnit: {
    type: Number,
    required: false,
  },
  featureFlooring: {
    type: String,
    required: false,
  },
  featureExterior: {
    type: String,
    required: false,
  },
  featureConcierge: {
    type: String,
    required: false,
  },
  indoorFeatures: {
    type: String,
    required: false,
  },
  outdoorFeatures: {
    type: String,
    required: false,
  },
  minBedrooms: {
    type: Number,
    required: false,
  },
  maxBedrooms: {
    type: Number,
    required: false,
  },
  minBathrooms: {
    type: Schema.Types.Decimal128,
    required: false,
  },
  maxBathrooms: {
    type: Schema.Types.Decimal128,
    required: false,
  },
  minParking: {
    type: Number,
    required: false,
  },
  maxParking: {
    type: Number,
    required: false,
  },
  csvVariationPending: {
    type: Boolean,
    required: false,
    default: false,
  },
  isPrivate: {
    type: Boolean,
    required: false,
    default: false,
  },
  status: {
    type: Boolean,
    required: false,
    default: false,
  },
  variations: [{
    type: Schema.Types.ObjectId,
    ref: 'ProjectVariation',
  }],
  deletedAt: {
    type: Date,
    required: false,
  }
}, {
  collection: 'projects',
  timestamps: true,
  strict: false,
});

What am I doing wrong? Thanks in advance.

Upvotes: 0

Views: 444

Answers (1)

mortillan
mortillan

Reputation: 41

Was able to make it work. Hope this helps others who are developing using node.js

The issue was caused by 2 things:

  1. Mongoose, when declaring subdocuments (nested objects or array of objects), gets confused when there is a type field in the document since in Mongoose concept, it is a reserved word for declaring the type of field. In my case, the type key comes from GeoJSON since it is required by MongoDB to be in that format. Here is a link from mongoose docs for better understanding.

What I just did is change PointSchema to this

import { Schema, Document } from 'mongoose';

interface Point extends Document {
  type: string,
  coordinates: Array<number>,
}

const PointSchema: Schema = new Schema({
  type: {
    $type: String,
    enum: ['Point'],
    required: true
  },
  coordinates: {
    $type: [Number],
    required: true
  }
}, {
  _id: false,
  typeKey: '$type',
});

export {
  Point,
  PointSchema,
}
  1. I'm also having a problem w/ circular dependency in node.js when importing/requiring the PointSchema. This error
/home/******/projects/realista-api/node_modules/mongoose/lib/schema.js:418 throw new TypeError('Invalid value for schema path ' + prefix + key + ''); ^ TypeError: Invalid value for schema path coordinates

occurred because PointSchema is undefined when I'm using it in ProjectSchema.

W/c justifies why issues in Mongoose Github suggest that mostly when they encounter that error, it is because of misspelled types (ObjectID instead of ObjectId), or in my case undefined w/c is invalid type.

Upvotes: 1

Related Questions