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.