Complex types and Azure Mobile Services – Android version

Last week I posted about storing objects which have properties with complex types in Azure Mobile Services. That post was based on the managed SDK, but I’ve seen since then a couple of posts asking about how to do the same for Android. Here’s a short post about this issue.

Recap

The problem with complex types is that the mobile service runtime doesn’t know how to insert into the database tables any properties whose values have complex types (such as arrays or objects). For example, if you have a Movie class defined as below (getter / setter omitted for brevity):

  1. public class Movie {
  2.     private int id;
  3.  
  4.     @SerializedName("title")
  5.     private String mTitle;
  6.     
  7.     @SerializedName("year")
  8.     private int mReleaseYear;
  9.  
  10.     @SerializedName("reviews")
  11.     private MovieReview[] mReviews;
  12. }

Which references the following MovieReview class:

  1. public class MovieReview {
  2.  
  3.     @SerializedName("stars")
  4.     private int mStars;
  5.     
  6.     @SerializedName("comment")
  7.     private String mComment;
  8. }

If we try to insert an item of type Movie into a table, it will fail with the error below:

HTTP/1.1 400 Bad Request
Cache-Control: no-cache
Content-Length: 112
Content-Type: application/json
Server: Microsoft-IIS/8.0
Date: Thu, 22 Aug 2013 22:10:11 GMT

{"code":400,"error":"Error: The value of property 'reviews' is of type 'object' which is not a supported type."}

And like it’s the case for C#, for Android there are also two ways to solve this issue: make the data, on the client side, of a type which the runtime understands, or “teach” the runtime to understand the non-primitive types, by using scripts for the table operations.

Client-side data manipulation

Similarly to what we did for the managed case, for Android apps we can tweak the JSON serializer so that it will convert the complex type (in this case, an array of objects) into a primitive type which the database can store. For gson, the serializer used in the Android SDK, we need to implement two interfaces, JsonSerializer<T> for customizing the serialization, and JsonDeserializer<T> for customizing the deserialization. This can be done in a single class:

  1. public class MovieReviewArraySerializer
  2.     implements JsonSerializer<MovieReview[]>, JsonDeserializer<MovieReview[]> {
  3.  
  4.     @Override
  5.     public JsonElement serialize(MovieReview[] value, Type type,
  6.             JsonSerializationContext context) {
  7.         String serialized = new Gson().toJson(value);
  8.         return new JsonPrimitive(serialized);
  9.     }
  10.  
  11.     @Override
  12.     public MovieReview[] deserialize(JsonElement element, Type type,
  13.             JsonDeserializationContext context) throws JsonParseException {
  14.         String serialized = element.getAsString();
  15.         JsonArray array = (JsonArray) new JsonParser().parse(serialized);
  16.         return new Gson().fromJson(array, MovieReview[].class);
  17.     }
  18.  
  19. }

What this implementation does is to convert the complex type to a JSON string using a new Gson serializer, and use that string itself as the property value. On deserialization, it first parses the incoming JSON string into a JSON object, then uses a new Gson serializer to read it back into the array of movie reviews. Now all we need to do is to register the serializers to the MobileServiceClient object, which exposes a helper method exactly for that:

  1. mClient_ComplexClientSide.registerSerializer(MovieReview[].class, new MovieReviewArraySerializer());
  2. mClient_ComplexClientSide.registerDeserializer(MovieReview[].class, new MovieReviewArraySerializer());

And now the call should work fine.

Server-side data manipulation

The other alternative is to not do anything on the client, but on the server we’d change the data so that we’d only be inserting primitive values to the database. Here there are also two options: one which is to do exactly what we did at the client side, by “stringifying” the complex type (i.e., denormalizing the relationship) – and how this can be done is shown at this post, so I won’t repeat it here. And if you want to keep the table normalized (i.e., having an actual 1:n relationship stored in the database), you can use the exact same server-side scripts which I had in the post about complex types in the managed/C# client.

That’s it. If you want to see the complete code for this example, I’ve posted it to the GitHub repository https://github.com/carlosfigueira/blogsamples, under https://github.com/carlosfigueira/blogsamples/tree/master/AzureMobileServices/ComplexTypesAndroid.