JSON within subclasses, across multiple programming languages

I was developing an expression tree on a project, i.e. made up of subclasses of the class Expression, such as AndExpression, MemberExpression, LiteralExpression etc. The main code is TypeScript/JavaScript but this needs to pass JSON to TypeScript/JavaScript, C# or Java code (and possibly other languages).

Now when JavaScript JSON.stringify does it’s thing we’re left with no type information making it problematic converting each type back to it’s actual type, i.e. to an AndExpression not just an Expression.

A relatively easy way to solve this, whilst not as elegant as one might hope is to store a string representing the type within the object, for example

export class LiteralExpression extends Expression {
  public readonly $type: string = "LiteralExpression"
}

When we run the expression through JSON.stringify we get JSON with “$type”:”AndExpression” for example. In JavaScript we still need to do some work to convert this back to JavaScript classes, it’s easy enough to use JSON.parse(json) then iterate over our expression objects converting to subclasses and revive our objects from JSON in this way.

Note the use of the variable name $type. It’s important that it’s named $type if you want it to easily be translated into C# objects with Json.NET as this name is hard coded in this library, whereas Java’s jackson JAR allows us to easily change the name/key used.

Json.NET (Newtonsoft.Json)

Sadly we don’t quite get everything for free using Json.NET because it’s expecting C# style naming for classes, i.e. assembly/namespace etc. The easiest way to deal with this it to serialize/deserialize using our own SerializationBinder, for example

public static class Serialization
{
  public class KnownTypesBinder : ISerializationBinder
  {
    public IList<Type> KnownTypes { get; set; }

    public Type BindToType(string assemblyName, string typeName)
    {
      return KnownTypes.SingleOrDefault(t => t.Name == typeName);
    }

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
      assemblyName = null;
      typeName = serializedType.Name;
    }
  }

  private static KnownTypesBinder knownTypesBinder = new KnownTypesBinder
  {
    KnownTypes = new List<Type>
    {
      typeof(AndExpression),
      typeof(BinaryExpression),
      typeof(LiteralExpression),
      typeof(LogicalExpression),
      typeof(MemberExpression),
      typeof(NotExpression),
      typeof(OperatorExpression),
      typeof(OrExpression)
    }
  };

  public static string Serialize(Expression expression)
  {
    var json = JsonConvert.SerializeObject(
      expression, 
      Formatting.None, 
      new JsonSerializerSettings
    {
      TypeNameHandling = TypeNameHandling.Objects,
      SerializationBinder = knownTypesBinder
    });
    return json;
  }

  public static Expression Deserialize(string json)
  {
    return JsonConvert.DeserializeObject<Expression>(
      json, 
      new JsonSerializerSettings
    {
      TypeNameHandling = TypeNameHandling.Objects,
      SerializationBinder = knownTypesBinder
    });
  }
}

Don’t forget you’ll also need to mark your properties and/or constructor parameters with [JsonProperty(“left”)] especially if you have situations where the names are keywords.

com.fasterxml.jackson.core

In Java we can add the following dependency to our pom.xml

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.10.0</version>
</dependency>

Now in our Expression base class we write the following

import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "$type")
public class Expression {
}

This tells jackson to include the type info using the keyword $type.

We also need to add the @JsonCreator annotation to each of our classes and each constructor parameter requires the following annotation @JsonProperty(“left”). Finally to serialize/deserialize we create an ObjectMapper to allow us to map our types to real objects using

package com.rbs.expressions;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;

public class Serialization {

  private static ObjectMapper createMapper() {
    ObjectMapper mapper = new ObjectMapper();

    mapper.registerSubtypes(
      new NamedType(AndExpression.class, "AndExpression"),
      new NamedType(BinaryExpression.class, "BinaryExpression"),
      new NamedType(OrExpression.class, "OrExpression"),
      new NamedType(LiteralExpression.class, "LiteralExpression"),
      new NamedType(LogicalExpression.class, "LogicalExpression"),
      new NamedType(MemberExpression.class, "MemberExpression"),
      new NamedType(NotExpression.class, "NotExpression"),
      new NamedType(OperatorExpression.class, "OperatorExpression")
    );

    return mapper;
  }

  public static String serialize(Expression expression) throws JsonProcessingException {
    return createMapper().writeValueAsString(expression);
  }

  public static Expression deserialize(String json) throws JsonProcessingException {
    return createMapper().readValue(json, Expression.class);
  }
}