Serializing Manatee.Json in .NET Core 3

半腔热情 提交于 2020-01-25 10:14:08

问题


Background
I want to provide some JsonSchema from my .NET Core 3 application, as well as other objects serialized into JSON. Since Manatee.Json is frequently updated and provides good support for JsonSchema, they are my preferred choice. At the same time, .NET Core 3 provides excellent support for returning objects with magic that transform them into JSON documents.

Example:

public ActionResult<MyFancyClass> MyFancyAction()
{
    return new MyFancyClass {
        Property1 = "property 1 content",
        Property2 = "property 2 content",
    };
}

Output:

{
    "Property1": "property 1 content",
    "Property2": "property 2 content"
}

Problem
.NET Core 3 has internal support for JSON with its System.Text.Json which is used in the previous example. If I try to serialize Manatee.Json.Schema.JsonSchema, its internal structure are serialized, not the json schema itself.

Example:

public ActionResult<MyFancyClass2> MyFancyAction2()
{
    return new MyFancyClass2 {
        Property1 = "property 1 content",
        Property1Schema = new JsonSchema()
            .Type(JsonSchemaType.String)
    };
}

Output:

{
  "Property1": "property 1 content",
  "Property1Schema": [{
    "name":"type",
    "supportedVersions":15,
    "validationSequence":1,
    "vocabulary": {
      "id":"https://json-schema.org/draft/2019-09/vocab/validation",
      "metaSchemaId":"https://json-schema.org/draft/2019-09/meta/validation"
    }
  }]
}

I expect this:

{
  "Property1": "property 1 content",
  "Property1Schema": {
    "type": "string",
  }
}

Manatee.Json.JsonValue also have a conflicting inner structure, where System.Text.Json.JsonSerializer fails to access internal get methods correctly and I get for instance this exception message:

Cannot access value of type Object as type Boolean.

Discovery
Manatee.Json.Schema.JsonSchema has a .ToJson() method that can be used to get the correct json schema as a JsonValue, but then I get the problem I just mentioned with serializing Manatee.Json.JsonValue.

Question
Does anyone know a way to enable System.Text.Json to serialize Manatee.Json structures?

Sidemark
Another way forward is to replace System.Text.Json altogether (take a look at this question).


回答1:


.NET Core 3 json serialization comes with a lot of configuration options. One of them is to add converters that specify how different types should be serialized. One way forward is to create a JsonConverter for JsonSchema and another for JsonValue.

For JsonSchema we can implement a JsonSchemaConverter that when serializing/writing, extracts the json schema as a JsonValue and ask the serializer to serialize that JsonValue instead. Like this:

public class JsonSchemaConverter : JsonConverter<JsonSchema>
{
    public JsonSchemaConverter()
    {
        _manateeSerializer = new ManateeSerializer();
    }

    private ManateeSerializer _manateeSerializer { get; set; }

    public override JsonSchema Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var jsonText = reader.GetString();
        var jsonValue = JsonValue.Parse(jsonText);
        return _manateeSerializer.Deserialize<JsonSchema>(jsonValue);
    }

    public override void Write(Utf8JsonWriter writer, JsonSchema value, JsonSerializerOptions options)
    {
        var schemaAsJson = value.ToJson(_manateeSerializer);
        try
        {
            System.Text.Json.JsonSerializer.Serialize<JsonValue>(writer, schemaAsJson, options);
        }
        catch (Exception e)
        {
            Log.Information($"Failed to serialize JsonSchema ({e.Message});");
            writer.WriteNullValue();
        }
    }
}

For JsonValue we can change it into something System.Text.Json understands, since it is json after all. One unfortunate approach is to serialize the JsonValue to a string, parsing it with for instance JsonDocument.Parse(string) and serialize its RootElement property. It feels so unnecessary to go via JsonDocument, so if anyone finds a better solution that would be great! A possible implementation can look like this:

public class JsonValueConverter : JsonConverter<JsonValue>
{
    public override JsonValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var json = reader.GetString();
        return JsonValue.Parse(json);
    }

    public override void Write(Utf8JsonWriter writer, JsonValue value, JsonSerializerOptions options)
    {
        string content = value.ToString();
        try
        {
            var jsonDocument = JsonDocument.Parse(content);
            JsonSerializer.Serialize<JsonElement>(writer, jsonDocument.RootElement, options);
        }
        catch (Exception e)
        {
            Log.Warning($"JsonDocument.Parse(JsonValue) failed in JsonValueConverter.Write(,,).\n{e.Message}");
            writer.WriteNullValue();
        }
    }
}

They must be registered at Startup.cs like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Converters.Add(new JsonValueConverter());
            options.JsonSerializerOptions.Converters.Add(new JsonSchemaConverter());
        });
}


来源:https://stackoverflow.com/questions/59855362/serializing-manatee-json-in-net-core-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!