使命名元组的名称出现在序列化的 JSON 响应中 [英] Make names of named tuples appear in serialized JSON responses

查看:21
本文介绍了使命名元组的名称出现在序列化的 JSON 响应中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

情况:我有多个传递对象结构的 Web 服务 API 调用.目前,我声明了显式类型以将这些对象结构绑定在一起.为简单起见,举个例子:

Situation: I have multiple Web service API calls that deliver object structures. Currently, I declare explicit types to bind those object structures together. For the sake of simplicity, here's an example:

[HttpGet]
[ProducesResponseType(typeof(MyType), 200)]
public MyType TestOriginal()
{
    return new MyType { Speed: 5.0, Distance: 4 };
}

改进:我有很多这些自定义类,例如 MyType,我希望改用通用容器.我遇到了命名元组,并且可以在我的控制器方法中成功使用它们,如下所示:

Improvement: I have loads of these custom classes like MyType and would love to use a generic container instead. I came across named tuples and can successfully use them in my controller methods like this:

[HttpGet]
[ProducesResponseType(typeof((double speed, int distance)), 200)]
public (double speed, int distance) Test()
{
    return (speed: 5.0, distance: 4);
}

问题我面临的是解析的类型是基于底层的 Tuple 包含这些无意义的属性 Item1, Item2 等示例:

Problem I am facing is that the resolved type is based on the underlying Tuple which contains these meaningless properties Item1, Item2 etc. Example:

问题:有没有人找到将命名元组的名称序列化到我的 JSON 响应中的解决方案?或者,有没有人找到一个通用的解决方案,允许为 random 结构提供单个类/表示,以便 JSON 响应明确命名它包含的内容.

Question: Has anyone found a solution to get the names of the named tuples serialized into my JSON responses? Alternatively, has anyone found a generic solution that allows to have a single class/representation for random structures that can be used so that the JSON response explicitly names what it contains.

推荐答案

对于序列化响应,只需在操作和自定义合同解析器上使用任何自定义属性(不幸的是,这只是解决方案,但我仍在寻找任何更优雅的一个).

For serializing response just use any custom attribute on action and custom contract resolver (this is only solution, unfortunately, but I'm still looking for any more elegance one).

属性:

public class ReturnValueTupleAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        var content = actionExecutedContext?.Response?.Content as ObjectContent;
        if (!(content?.Formatter is JsonMediaTypeFormatter))
        {
            return;
        }

        var names = actionExecutedContext
            .ActionContext
            .ControllerContext
            .ControllerDescriptor
            .ControllerType
            .GetMethod(actionExecutedContext.ActionContext.ActionDescriptor.ActionName)
            ?.ReturnParameter
            ?.GetCustomAttribute<TupleElementNamesAttribute>()
            ?.TransformNames;

        var formatter = new JsonMediaTypeFormatter
        {
            SerializerSettings =
            {
                ContractResolver = new ValueTuplesContractResolver(names),
            },
        };

        actionExecutedContext.Response.Content = new ObjectContent(content.ObjectType, content.Value, formatter);
    }
}

ContractResolver:

public class ValueTuplesContractResolver : CamelCasePropertyNamesContractResolver
{
    private IList<string> _names;

    public ValueTuplesContractResolver(IList<string> names)
    {
        _names = names;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);
        if (type.Name.Contains(nameof(ValueTuple)))
        {
            for (var i = 0; i < properties.Count; i++)
            {
                properties[i].PropertyName = _names[i];
            }

            _names = _names.Skip(properties.Count).ToList();
        }

        return properties;
    }
}

用法:

[ReturnValueTuple]
[HttpGet]
[Route("types")]
public IEnumerable<(int id, string name)> GetDocumentTypes()
{
    return ServiceContainer.Db
        .DocumentTypes
        .AsEnumerable()
        .Select(dt => (dt.Id, dt.Name));
}

这个返回下一个 JSON:

This one returns next JSON:

[  
   {  
      "id":0,
      "name":"Other"
   },
   {  
      "id":1,
      "name":"Shipping Document"
   }
]

<小时>

Swagger UI 的解决方案如下:

public class SwaggerValueTupleFilter : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var action = apiDescription.ActionDescriptor;
        var controller = action.ControllerDescriptor.ControllerType;
        var method = controller.GetMethod(action.ActionName);
        var names = method?.ReturnParameter?.GetCustomAttribute<TupleElementNamesAttribute>()?.TransformNames;
        if (names == null)
        {
            return;
        }

        var responseType = apiDescription.ResponseDescription.DeclaredType;
        FieldInfo[] tupleFields;
        var props = new Dictionary<string, string>();
        var isEnumer = responseType.GetInterface(nameof(IEnumerable)) != null;
        if (isEnumer)
        {
            tupleFields = responseType
                .GetGenericArguments()[0]
                .GetFields();
        }
        else
        {
            tupleFields = responseType.GetFields();
        }

        for (var i = 0; i < tupleFields.Length; i++)
        {
            props.Add(names[i], tupleFields[i].FieldType.GetFriendlyName());
        }

        object result;
        if (isEnumer)
        {
            result = new List<Dictionary<string, string>>
            {
                props,
            };
        }
        else
        {
            result = props;
        }

        operation.responses.Clear();
        operation.responses.Add("200", new Response
        {
            description = "OK",
            schema = new Schema
            {
                example = result,
            },
        });
    }

这篇关于使命名元组的名称出现在序列化的 JSON 响应中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆