corvus-dotnet / Corvus.Extensions.Newtonsoft.Json

Opinionated configuration for Newtonsoft.Json including Microsoft DI support. Sponsored by endjin.
Apache License 2.0
2 stars 0 forks source link

Corvus.Extensions.Newtonsoft.Json

Build Status GitHub license IMM

This provides opinionated configuration and DI support for Newtonsoft.Json serialization.

It is built for netstandard2.0.

Features

IJsonSerializationSettingsProvider

It is common to need to configure and manage a consistent set of JsonSerializerSettings across multiple components, to ensure succesful interop between services, both within and between hosts.

In order to support this, we have an IJsonSerializerSettingsProvider service which has a single Instance property which (as the naming implies) gives you an instance of Newtonsoft's JsonSerializerSettings, in a known configuration.

We also supply a standard implementation of this service called JsonSerializationSettingsProvider.

This is configured for enum serialization as strings, camelCase property names, no dictionary key mapping, ignored null values, and no special-case DateTime handling.

You can see the current defaults here.

One feature of this implementation is that it takes an enumerable of JsonConverter objects in its constructor. If you register it in the Microsoft.Extensions.DependencyInjection container using the IServiceCollection extension method called AddJsonSerializerSettings(), then you get the powerful feature that it will then have its converters configured from the container too. Components that wish to add their converters to the standard settings need only add them to the container.

The default implementation does not add any converters, but we provide several standard converters as part of the library. These can be individually added via IServiceCollection extension methods defined in JsonSerializerSettingsProviderServiceCollectionExtensions.

Provided JsonConverters

DateTimeOffsetConverter

(Nullable) DateTimeOffset (which converts to/from json of the form {"dateTimeOffset":"<Roundtrippable string format>", "unixTime": <long of unix milliseconds>}, and from standard JSON date strings.

This is useful when serializing DateTimeOffset instances to a store that provides indexing and querying functionality (e.g. CosmosDb). Standard serialization of DateTimeOffset uses ISO 8601 format, which includes the timezone offset in the resultant value. This makes it impossible to sort or perform range queries on the stored date/time values.

When the DateTimeOffsetConverter is used, the contained dateTimeOffset value retains the original value (including timezone information) and the "unixTime" value contains a numeric representation of the date that can be used for sorting and filtering.

CultureInfoConverter

which converts to/from the culture name string e.g. en-GB, fr-FR

PropertyBag

A handy serializable property bag which converts to/from strongly typed key value pairs, internally stored in a JSON representation.

PropertyBag support is enabled by using the JsonSerializerSettingsProviderServiceCollectionExtensions.AddJsonNetPropertyBag extension method as part of your DI configuration.

PropertyBag instances are created via the IPropertyBagFactory implementation. The current implementation uses Json.Net for serialization/deserialization.

You can construct an empty PropertyBag

IPropertyBag propertyBag = propertyBagFactory.Create(PropertyBagValues.Empty);

Or from an IDictionary<string,object>

IDictionary<string,object> someDictionary;
IPropertyBag propertyBag = propertyBagFactory.Create(someDictionary);

If you need to create an IPropertyBag from an existing JObject, you must take a dependency on IJsonNetPropertyBagFactory instead of IPropertyBagFactory.

JObject someJObject;
IPropertyBag propertyBag = jsonNetPropertyBagFactory.Create(someJObject);

You can then retrieve strongly typed values from the property bag.


int myValue = 3;
var myObject = new SomeType("Hello world", 134.6);

propertyBag.TryGet("property1", out int myRetrievedValue); // returns true
propertyBag.TryGet("property2", out SomeType myRetrievedObject); // returns true
propertyBag.TryGet("property3", out double wontWork); // returns false 

Internally, it stores the values using a JSON representation. This means that you can happily set as one type, and retrieve as another, as long as your serializer supports that conversion.

This means that .NET types that don't have a direct JSON equivalent (e.g. DateTime) will be stored internally as strings and can be retrieved either as the native .NET type or as a string.

public class SomeType
{
  public SomeType()
  {
  }

  public SomeType(string property1, int property2)
  {
    this.Property1 = property1;
    this.Property2 = property2;
  }

  public string Property1 { get; set; }
  public int Property2 { get; set; }
}

public class SomeSemanticallySimilarType
{
  public SomeSemanticallySimilarType()
  {
  }

  public SomeSemanticallySimilarType(string property1, int property2)
  {
    this.Property1 = property1;
    this.Property2 = property2;
  }

  public string Property1 { get; set; }
  public int Property2 { get; set; }
  public bool? Property3 { get; set; }
}

propertyBag.Set("key1", new SomeType("Hello", 3));
propertyBag.TryGet("key1", out SomeSemanticallySimilarType myRetrievedObject); // returns true

PropertyBags are immutable. Modified bags can be created using the IPropertyBagFactory.CreateModified method.

IPropertyBag someExistingPropertyBag;

IDictionary<string,object> itemsToAddOrUpdate;
IList<string> keysOfItemsToRemove;

IPropertyBag propertyBag = propertyBagFactory.CreateModified(
    someExistingPropertyBag,
    itemsToAddOrUpdate,
    keysOfItemsToRemove);

Dynamic discovery of items

There are multiple ways in which the contents of a PropertyBag can be discovered at runtime.

IPropertyBag instances are expected to implement IEnumerable<(string Key, PropertyBagEntryType Type)>. This allows you to enumerate an IPropertyBag to discover its members and their types. The PropertyBagEntryType enumeration lists the types supported in JSON, with the additional nuance that it differentiates between integer and floating point numbers.

We also provide an extension method AsDictionary() on IPropertyBag which uses the enumeration to convert the property bag to a IReadOnlyDictionary<string, object>. Values in this dictionary will be .NET native types where possible:

It is also possible to use the AsDictionaryRecursive extension method which follows the same rules as AsDictionary() with the exception that nested objects are also converted to IReadOnlyDictionary<string, object>.

When using the Json.NET-based implementation of IPropertyBagFactory and IPropertyBag, instances of IPropertyBag can be converted to a JObject (which can also be used for dynamic scenarios). This is done via the IJsonNetPropertyBagFactory:

IPropertyBag someExistingPropertyBag;
JObject result = jsonNetPropertyBagFactory.AsJObject(someExistingPropertyBag);
JsonSerializerSettings

Internally, all the property values are stored as properties on a JObject, which requires serializion/deserialization. IPropertyBag instances are supplied their JsonSerializerSettings by the IJsonNetPropertyBagFactory implementation, which receives them from the service provider on creation via the IJsonSerializerSettingsProvider.

Note that if you modify the default serializer settings, this can have an impact on the behaviour of the AsDictionary and AsDictionaryRecursive extension methods. For example, if you set DateParseHandling to DateParseHandling.DateTime or DateParseHandling.DateTimeOffset and then convert a deserialized IPropertyBag instance to a dictionary, you may find that your date/time fields have been converted to strings using a non-standard format.

Licenses

GitHub license

Corvus.Extensions.Newtonsoft.Json is available under the Apache 2.0 open source license.

For any licensing questions, please email licensing@endjin.com

Project Sponsor

This project is sponsored by endjin, a UK based Microsoft Gold Partner for Cloud Platform, Data Platform, Data Analytics, DevOps, and a Power BI Partner.

For more information about our products and services, or for commercial support of this project, please contact us.

We produce two free weekly newsletters; Azure Weekly for all things about the Microsoft Azure Platform, and Power BI Weekly.

Keep up with everything that's going on at endjin via our blog, follow us on Twitter, or LinkedIn.

Our other Open Source projects can be found on GitHub

Code of conduct

This project has adopted a code of conduct adapted from the Contributor Covenant to clarify expected behavior in our community. This code of conduct has been adopted by many other projects. For more information see the Code of Conduct FAQ or contact hello@endjin.com with any additional questions or comments.

IP Maturity Matrix (IMM)

The IMM is endjin's IP quality framework.

Shared Engineering Standards

Coding Standards

Executable Specifications

Code Coverage

Benchmarks

Reference Documentation

Design & Implementation Documentation

How-to Documentation

Date of Last IP Review

Framework Version

Associated Work Items

Source Code Availability

License

Production Use

Insights

Packaging

Deployment

OpenChain