Show / Hide Table of Contents

JSON Serialization

Starting with v0.8.2, LambdaSharp uses System.Text.Json v5.0 instead of Newtonsoft.Json for JSON serialization of built-in types. Custom types are handled with the JSON serializer specified using the LambdaSerializer assembly attribute.

This article describes how to switch from the default Newtonsoft.Json to System.Text.Json, as well as what to look out for.

Migrating JSON Serialization from Newtonsoft.Json to System.Text.Json

Lambda functions using System.Text.Json must declare LambdaSystemTextJsonSerializer as their JSON serializer using the JsonSerializer assembly attribute.

[assembly: Amazon.Lambda.Core.LambdaSerializer(typeof(LambdaSharp.Serialization.LambdaSystemTextJsonSerializer))]

Microsoft has published an excellent migration guide for switching from Newtonsoft.Json to System.Text.Json. In addition to the guide, the following sections explain how to migrate existing data structures.

Alternatively, functions can continue to use Newtonsoft.Json as their JSON serializer by including the LambdaSharp.Serialization.NewtonsoftJson assembly from NuGet:

[assembly: Amazon.Lambda.Core.LambdaSerializer(typeof(LambdaSharp.Serialization.LambdaNewtonsoftJsonSerializer))]

Update Projects

Upgrade projects to .NET Core 3.1 by changing the target framework in the .csproj file.

  • Before: <TargetFramework>netcoreapp2.1</TargetFramework>
  • After: <TargetFramework>netcoreapp3.1</TargetFramework>

Remove all Newtonsoft.Json package dependencies (version may vary).

  • Remove: <PackageReference Include="Newtonsoft.Json" />

Replace Non-Public Properties/Fields with Public Properties/Fields

Unlike Newtonsoft.Json, System.Text.Json does not serialize non-public properties/fields.

Non-public properties must be converted to public, mutable properties.

  • Before: internal string Name { get; set; }
  • After: public string Name { get; set; }

Limited mutable properties must be converted to public, mutable properties to be deserialized properly.

  • Before: public string Name { get; protected set; }
  • After: public string Name { get; set; }

Non-public fields must be converted to public, mutable fields.

  • Before: internal string Name;
  • After: public string Name;

Convert JSON string values to enum properties

Newtonsoft.Json provides StringEnumConverter to convert JSON string to enum properties. System.Text.Json includes an equivalent converter called JsonStringEnumConverter in the System.Text.Json.Serialization namespace.

  • Before: [JsonConverter(typeof(StringEnumConverter))]
  • After: [JsonConverter(typeof(JsonStringEnumConverter))]

Convert JSON integer values to DateTimeOffset/DateTime properties

Newtonsoft.Json provides UnixDateTimeConverter to convert JSON integer to DateTime properties. System.Text.Json does not include such a converter. Instead, LambdaSharp.Serialization defines JsonEpochSecondsDateTimeOffsetConverter and JsonEpochSecondsDateTimeConverter to convert DateTimeOffset and DateTime, respectively to a JSON integer representing the UNIX epoch in seconds.

[JsonConverter(typeof(JsonEpochSecondsDateTimeOffsetConverter))]
public DateTimeOffset Epoch { get; set; }

--OR--

[JsonConverter(typeof(JsonEpochSecondsDateTimeConverter))]
public DateTime Epoch { get; set; }

Update Property Attributes

Replace attribute for explicitly naming JSON elements.

  • Before: [JsonProperty("name")]
  • After: [JsonPropertyName("name")]
  • Requires: using System.Text.Json.Serialization;

Replace attribute for requiring a JSON property (used by JSON schema generator for API Gateway models)

  • Before: [JsonRequired] -or- [JsonProperty(Required = Required.DisallowNull)]
  • After: [Required]
  • Requires: using System.ComponentModel.DataAnnotations;

Case-Sensitive Serialization

Newtonsoft.Json is not case-sensitive on property/field names, but System.Text.Json is.

Solution 1: Use Newtonsoft.Json Serializer

Keep using the Newtonsoft.Json serializer instead by adding the LambdaSharp.Serialization.NewtonsoftJson NuGet package and assembly attribute for it.

[assembly: Amazon.Lambda.Core.LambdaSerializer(typeof(LambdaSharp.Serialization.LambdaNewtonsoftJsonSerializer))]`

Solution 2: Provide Proper Case-Sensitive Spelling for Property/Field

Use the [JsonPropertyName("name")] attribute to provide the property/field name with the case-sensitive spelling.

class MyClass {

    //--- Properties ---
    [JsonPropertyName("name")]
    public string Name { get; set; }
}

Solution 3: Custom System.Text.Json Serializer Settings

Create a custom serializer that overrides the default System.Text.Json behavior in its constructor.

[assembly: Amazon.Lambda.Core.LambdaSerializer(typeof(MySerializer))]

public class MySerializer : LambdaSharp.Serialization.LambdaSystemTextJsonSerializer {

    //--- Constructors ---
    public MySerializer() : base(settings => {
        settings.settings.PropertyNameCaseInsensitive = true;
    }) { }
}

Derived Classes Serialization

Beware of derived classes during serialization. System.Text.Json will only serialize properties of the declared type, not all the properties of the actual instance, unless you use object as type.

  • Before: LambdaSerializer.Serialize<Car>(new Sedan { ... }) (only Car properties are serialized; any additional Sedan properties are skipped)
  • After: LambdaSerializer.Serialize<object>(mySedan) (all public properties are serialized)

Polymorphic Serialization

Additional care is required when serializing an abstract syntax tree where nodes share an abstract base definition.

For example, consider the definition for nestable lists with values. Using Newtonsoft.Json, we may have written something as follows, where ListConverter and ValueConverter are JSON-converters that implement serialization for their respective types.

public class AExpression { }

[JsonConverter(ListConverter)]
public class List : AExpression {
    public List<AExpression> Items { get; set; } = new List<AExpression>()
}

[JsonConverter(LiteralConverter)]
public class Literal : AExpression {
    public string Value { get; set; }
}

Surprisingly, the following expression serializes as [{}] because the declared type for List is AExpression which has no properties!

JsonSerialize.Serialize(new List {
    Items = {
        new Literal {
            Value = 123
        }
    }
})

The solution is to provide a JsonConverter for AExpression that knows how to perform polymorphic serialization based on the actual derived type.

public class ExpressionConverter : JsonConverter<AExpression> {

    //--- Class Fields ---
    private readonly ListConverter _listSerializer = new ListConverter();
    private readonly LiteralConverter _literalSerializer = new LiteralConverter();

    //--- Methods ---
    public override ACloudFormationExpression Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {

        // Read() implementation omitted for brevity
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, ACloudFormationExpression value, JsonSerializerOptions options) {
        switch(value) {
        case List list:
            _listSerializer.Write(writer, list, options);
            break;
        case Literal literal:
            _literalSerializer.Write(writer, literal, options);
            break;
        default:
            throw new ArgumentException($"unsupported serialization type {value?.GetType().FullName ?? "<null>"}");
        }
    }
}

Custom JSON Serializer

Custom JSON serializer implementation can also be used by providing a class that derives from ILambdaJsonSerializer.

For example, the following JSON serializer uses LitJSON instead.

[assembly: Amazon.Lambda.Core.LambdaSerializer(typeof(MySerializer))]

public class MySerializer : ILambdaJsonSerializer {

    //--- Methods ---
    public object Deserialize(Stream stream, Type type) {
        var reader = new StreamReader(stream);
        var json = reader.ReadToEnd();
        return LitJson.JsonMapper.ToObject(json, type);
    }

    public T Deserialize<T>(Stream requestStream) => (T)Deserialize(stream, typeof(T));

    public void Serialize<T>(T response, Stream responseStream) {
        var json = LitJson.JsonMapper.ToJson(response);
        responseStream.Write(Encoding.UTF8.GetBytes(json));
    }
}
In This Article
Back to top Generated by DocFX