source

명명된 튜플의 이름을 직렬화된 JSON 응답에 표시

manysource 2023. 2. 8. 19:45

명명된 튜플의 이름을 직렬화된 JSON 응답에 표시

상황:오브젝트 구조를 전달하는 웹 서비스 API 호출이 여러 개 있습니다.현재 이러한 오브젝트 구조를 바인드하기 위한 명시적 타입을 선언하고 있습니다.알기 쉽게 하기 위해 예를 다음에 제시하겠습니다.

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

개선점:이런 커스텀 클래스가 많이 있어요MyType대신 일반 컨테이너를 사용하고 싶습니다.이름이 붙은 튜플이 발견되어 컨트롤러 방식에서 다음과 같이 정상적으로 사용할 수 있습니다.

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

내가 직면하고 있는 문제는 해결된 유형이 기본을 기반으로 한다는 것이다.Tuple이런 무의미한 특성들을 포함하고 있다.Item1,Item2기타 예:

여기에 이미지 설명 입력

질문:명명된 튜플의 이름을 JSON 응답에 연속적으로 삽입할 수 있는 솔루션을 찾은 사람이 있습니까?또는 JSON 응답에 포함되는 것을 명시적으로 명명할 수 있도록 사용할 수 있는 랜덤 구조의 단일 클래스/표현을 가능하게 하는 범용 솔루션을 발견한 사람이 있습니까?

명명된 튜플을 사용하는 경우의 문제는 튜플이 단지 구문 설탕이라는 것입니다.

named-and-named-tuples 매뉴얼을 체크하면 다음과 같은 부품이 있습니다.

이러한 동의어는 컴파일러와 언어로 처리되므로 명명된 튜플을 효과적으로 사용할 수 있습니다.IDE 및 편집자는 Roslyn API를 사용하여 이러한 의미 이름을 읽을 수 있습니다.명명된 튜플의 요소를 동일한 어셈블리 내의 임의의 시멘틱 이름으로 참조할 수 있습니다.컴파일러는 컴파일된 출력을 생성할 때 정의한 이름을 Item*과 동등한 이름으로 바꿉니다. 컴파일된 Microsoft Intermediate Language(MSIL)에는 이러한 요소에 지정된 이름이 포함되어 있지 않습니다.

따라서 컴파일이 아닌 런타임에 시리얼라이제이션이 이루어지기 때문에 문제가 발생하며 컴파일 중에 손실된 정보를 사용하려고 합니다.명명된 튜플 이름을 기억하기 위해 컴파일 전에 몇 가지 코드로 초기화하는 커스텀 시리얼라이저를 설계할 수도 있지만, 이 예에서는 이러한 복잡성이 너무 큰 것 같습니다.

응답의 시리얼화를 위해서는 액션과 커스텀 계약 해결사의 커스텀 속성을 사용해 주십시오(유감스럽게도 이것은 솔루션일 뿐이지만, 아직우아한 것을 찾고 있습니다).

속성:

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);
    }
}

계약 해결 방법:

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을 반환합니다.

[  
   {  
      "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,
            },
        });
    }

대신 익명 개체를 사용하십시오.

(double speed, int distance) = (5.0, 4);
return new { speed, distance };

입찰이 약간 모순되는 요구 사항을 가지고 있습니다.

질문:.

는 런런 like like like like i like i i i i 。MyType를 사용하는 .

코멘트:

그러나 반환할 항목을 명시적으로 표시하려면 ProductsResponseType 속성에 어떤 유형을 선언해야 합니까?

상기에 근거해, 기존의 타입을 계속 사용할 필요가 있습니다.이러한 유형은 다른 개발자/독자 또는 몇 개월 후 자신에게 유용한 문서를 제공합니다.

가독성 측면에서

[ProducesResponseType(typeof(Trip), 200)]

그때가 더 나을 것이다

[ProducesResponseType(typeof((double speed, int distance)), 200)]


속성 추가/제거는 한 곳에서만 수행해야 합니다.일반적인 접근법에서는 업데이트 특성도 기억해야 합니다.

은 '우리'를 사용하는 입니다.dynamicC#의 ExpandoObject는 API가 원하는 형식으로 응답을 래핑합니다.

    public JsonResult<ExpandoObject> GetSomething(int param)
    {
        var (speed, distance) = DataLayer.GetData(param);
        dynamic resultVM = new ExpandoObject();
        resultVM.speed= speed;
        resultVM.distance= distance;
        return Json(resultVM);
    }

" " 의 반환 :GetData" " specificed" (스위치 정보)

(decimal speed, int distance)

그러면 Json이 원하는 방식으로 응답합니다.

언급URL : https://stackoverflow.com/questions/45932003/make-names-of-named-tuples-appear-in-serialized-json-responses