Skip to content

Newtonsoft.Json

FunQL uses System.Text.Json by default for JSON serialization. However, if your project requires Newtonsoft.Json (JSON.NET), FunQL can seamlessly integrate it for both serialization and deserialization.

This section explains how to integrate Newtonsoft.Json with FunQL for both parsing (deserializing) and printing (serializing) constants.

Configuring deserialization

To parse FunQL constants (e.g., true, "name", 123, or objects/arrays) using Newtonsoft.Json, we will need to override the default IConstantParser. First, we create a parser that uses Newtonsoft.Json, and then we configure our schema to use it.

1. Create the parser

Implement a custom IConstantParser that uses JsonConvert.DeserializeObject() to deserialize JSON strings into FunQL constants.

/// <summary>
/// Implementation of <see cref="IConstantParser"/> that uses <see cref="JsonConvert"/> to parse the
/// <see cref="Constant"/> node.
/// </summary>
/// <param name="jsonSerializerSettings">Settings for <see cref="Newtonsoft.Json"/>.</param>
/// <inheritdoc/>
public class NewtonsoftJsonConstantParser(
    JsonSerializerSettings jsonSerializerSettings
) : IConstantParser
{
    /// <summary>Options for <see cref="Newtonsoft.Json"/>.</summary>
    private readonly JsonSerializerSettings _jsonSerializerSettings = jsonSerializerSettings;

    /// <inheritdoc/>
    public Constant ParseConstant(IParserState state)
    {
        state.IncreaseDepth();

        var expectedType = state.RequireContext<ConstantParseContext>().ExpectedType;
        // If Type is a primitive/struct (int, double, DateTime, etc.), we should make it Nullable as 'null' is a valid
        // constant, but JsonConvert can only read 'null' if Type can be null
        expectedType = expectedType.ToNullableType();

        var token = state.CurrentToken();
        switch (token.Type)
        {
            case TokenType.String:
            case TokenType.Number:
            case TokenType.Boolean:
            case TokenType.Null:
            case TokenType.Object:
            case TokenType.Array:
                // Valid token for constants
                break;
            case TokenType.OpenBracket:
                // Handle OpenBracket token as Array 
                token = state.CurrentTokenAsArray();
                break;
            case TokenType.None:
            case TokenType.Eof:
            case TokenType.Identifier:
            case TokenType.OpenParen:
            case TokenType.CloseParen:
            case TokenType.Comma:
            case TokenType.Dot:
            case TokenType.Dollar:
            case TokenType.CloseBracket:
            default:
                // Invalid token for constants
                throw state.Lexer.SyntaxException($"Expected constant at position {token.Position}, but found '{token.Text}'.");
        }

        var metadata = state.CreateMetadata();

        try
        { 
            var value = JsonConvert.DeserializeObject(token.Text, expectedType, _jsonSerializerSettings);
            // Successfully parsed, so go to next token
            state.NextToken();

            state.DecreaseDepth();
            return new Constant(value, metadata);
        }
        catch (Exception e) when (e is JsonException)
        {
            throw new ParseException($"Failed to parse constant '{token.Text}' at position {token.Position}.", e);
        }
    }
}

Note

This code is based on the default implementation, adapted to use Newtonsoft.Json instead of System.Text.Json.

2. Configure Schema

To use the NewtonsoftJsonConstantParser, override the default IConstantParser in your schema's OnInitializeSchema method.

public sealed class ApiSchema : Schema { 
    protected override void OnInitializeSchema(ISchemaConfigBuilder schema) {
        schema.AddParseFeature(it =>
        {
            IConstantParser? constantParser = null;
            it.MutableConfig.ConstantParserProvider = _ => constantParser ??= new NewtonsoftJsonConstantParser(
                new JsonSerializerSettings()
            );
        });
    }
}

Now, when you parse a FunQL query, the NewtonsoftJsonConstantParser will be used to parse the constants. You can also configure the JsonSerializerSettings as needed, adding custom converters, naming strategies, etc.

Configuring serialization

When printing FunQL queries to a string, the Constant nodes are serialized to JSON using the IConstantPrintVisitor. First, we implement this class to use Newtonsoft.Json, and then we configure the schema to use this implementation.

1. Create the print visitor

The NewtonsoftJsonConstantPrintVisitor serializes FunQL constants into JSON strings using JsonConvert.SerializeObject().

/// <summary>Implementation of <see cref="IConstantPrintVisitor{TState}"/> using <see cref="JsonConvert"/>.</summary>
/// <param name="jsonSerializerSettings">Settings for <see cref="Newtonsoft.Json"/>.</param>
/// <inheritdoc cref="IConstantPrintVisitor{TState}"/>
public class NewtonsoftJsonConstantPrintVisitor<TState>(
    JsonSerializerSettings jsonSerializerSettings
) : ConstantVisitor<TState>, IConstantPrintVisitor<TState> where TState : IPrintVisitorState
{
    /// <summary>Settings to use when writing JSON.</summary>
    private readonly JsonSerializerSettings _jsonSerializerSettings = jsonSerializerSettings;

    /// <inheritdoc/>
    public override Task Visit(Constant node, TState state, CancellationToken cancellationToken) =>
        state.OnVisit(node, async ct =>
        {
            var jsonValue = JsonConvert.SerializeObject(node.Value, _jsonSerializerSettings);
            await state.Write(jsonValue, ct);
        }, cancellationToken);
}

Note

This code is based on the default implementation, adapted to use Newtonsoft.Json instead of System.Text.Json.

2. Configure Schema

To use the NewtonsoftJsonConstantPrintVisitor, override the default IConstantPrintVisitor in your schema's OnInitializeSchema method.

public sealed class ApiSchema : Schema { 
    protected override void OnInitializeSchema(ISchemaConfigBuilder schema) {
        schema.AddPrintFeature(it =>
        {
            IConstantPrintVisitor<IPrintVisitorState>? constantPrintVisitor = null;
            it.MutableConfig.ConstantPrintVisitorProvider = _ => 
                constantPrintVisitor ??= new NewtonsoftJsonConstantPrintVisitor<IPrintVisitorState>(
                    new JsonSerializerSettings()
                );
        });
    }
}

Now, when you print a FunQL query, the NewtonsoftJsonConstantPrintVisitor will be used to serialize the constants.