• Meteor
  • Tutorial

Create a Custom EJSON Type

EJSON supports the date type and binary type out of the box. And we've seen how EJSON works hand-in-hand with publications and also with Meteor methods. But what if we have a custom type, like this Address constructor up here? In my person object on Line 27, it now has four properties, a name, a createdAt, which is a Date, a file which is of type, unsigned int8Array, and then address, which is just equal to a new address. And so this should be of type, Address.

And you can see that the address takes two parameters, the city and the state. And then it just sets the city property and the state property on the resulting object. And we just have one method on the address prototype called toString, and that just prints out a string representation of the total address. So let's go over to the browser and just make sure we see how it works.

Because I declared the person object outside of an isClient or isServer block, it's available on both the client and the server. So we can play with it in the browser. If I type, person, and hit Enter, we can see that the address property is of type address. And I can call the toString method on that address property. And you can see that it prints San Francisco, California.

But over on the left in my code you can see that I have a publication where I'm adding this record to the People collection. And this record is the same one we were just playing with on the Client. And then in my Client, I subscribe to the People subscription, or People collection, and so we should be able to query the collection for that person that is sent down by the server. Let's see what that looks like.

So I'm just going to call this Person2, and we'll set that equal to the Peoplecollection.findOne. And what we can see here is that the address has been serialized into an object because EJSON doesn't know about our address type yet. And so if I type Person2.address.toString I just get back this generic object string.

So what we can do is create a custom EJSON type for this Address constructor. To add our custom type to EJSON we have to do a few things. First, we need to add a Custom type, we need to tell EJSON about it. So to do that, we can call the addType method on the EJSON object. And the first parameter will be the name of the type, which we'll call Address.

And the second parameter will be a function that takes a value as a parameter, and we'll look at that in a second. And what this function should do is return a new instance of the type. OK, we're going to come back to this method in a second.

But the other thing we need to do is add a few methods to our Address prototype. The first method we need to add is Clone. And this is just the contract that EJSON specifies for custom types, and you can read about it in the documentation as well. So the Clone method is just going to create a copy of this object, and the way that we could do that is just to return a new Address.

And we'll pass in the city, and the current state, and that will just create a new Address instance. The second method we need to implement is Equals. And this will take Other as a parameter, and the Other should be another Address instance. And so the first thing that we can do is we can say that if Other is not an instance of Address, we'll just return false.

Otherwise, what we can do is we could compare the city and states each of the Address objects. But an easier thing that we could do is just convert both to EJSON strings and compare the strings. So we'll say EJSON.stringifythis, and if that is equal to the resulting string on Other then it should be the same object.

OK, the next method we need to implement is called typeName. And this is pretty straightforward, it's just going to return the name of this type, which is Address. And it should match the name that we use down here in addType.

OK, and finally we need to implement a method called toJSONValue. This is a little bit confusing. What this is actually going to return is not JSON string, but an object that could be turned into a JSON string without any custom types. And so what we want to do is we can return an object here, and then object will have a city and a state.

Now this Object that we're returning from toJSONValue is the same object that will get passed in to our addType, or you could think of this function as fromJSONValue. And so knowing that, just to make sure we understand what's going on, let's print it to the console. But, it's basically going to be an object that has a city and a state. And so what we can do is when we get that value we can create a new address object and pass the and value.state as parameters.

And it looks like I'm getting an error, and I suspect that that's because I forgot to save and this.state, let's see if that fixes it, and it does. OK, so we can see that our fromJSONValue function down here has actually been called. And the value that was passed is this object that's has two properties city and state.

And so this time if I say Person2 is equal to people.findOne and we grab the person off of the People collection. I can see that the address has properly been deserialized into an address type. And so if I call the toString method on the address, I get San Francisco, California, like I expect.

So the ability to add custom EJSON types is really powerful because it allows us to pass those custom types over the wire. So to do that, we created, in this example, a custom type called Address. And to make it participate with the EJSON contract, we added the Clone, Equals, typeName and two JSON methods. And then we called the addType method on EJSON passing it the name of the type and a function to call to deserialize this value and create a new instance of an Address object.

And if this function fromJSONValue is confusing to you, just think of it this way, whatever is returned from the toJSONValue function, whether it be an object, or a string, or any other valid object that can be turned into JSON that's what's going to get passed in as the parameter to this function. And so then we can use that to create new instances of our custom type. And so you can imagine a bunch of different ways that we can use this. For example, one way might be creating a custom model for use with our collections.