【问题标题】:Deserialize Root Object in ServiceStack Explicit Response DTO反序列化 ServiceStack 显式响应 DTO 中的根对象
【发布时间】:2018-02-20 07:40:24
【问题描述】:

我正在使用 ServiceStack 使用第三方 WebApi。想象一下这个 API 有以下路线。

https://api.example.com/v1/people/{id} 返回具有指定 ID 的人。

JSON:

{
    "id": 1,
    "name": "Jean-Luc Picard"
}

我可以使用以下 C# 代码来使用它。

class Program
{
    static void Main(string[] args)
    {
        var client = new JsonServiceClient("https://api.example.com/v1/");
        Person person = client.Get(new GetPerson() { ID = 1 });
    }
}

[Route("/people/{id}")]
public class GetPerson : IReturn<Person>
{
    public int ID { get; set; }
}

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
}

我想改用explicit response DTO,以防 API 发生变化。问题是 /people/{id} 端点返回的 JSON 是一个裸的 Person 对象。假设 v2 中的响应发生了变化。

JSON:

{
    "person": {
        "id": 1,
        "name": "Jean-Luc Picard"
    }
}

有了这个响应,下面的代码就可以工作了。

[Route("/people/{id}")]
public class GetPerson : IReturn<GetPersonResponse>
{
    public int ID { get; set; }
}

public class GetPersonResponse
{
    public Person Person { get; set; }
}

我想将这个GetPersonResponse 及其Person 属性用于上面不封装人员数据的当前JSON。我知道我们可以使用System.Runtime.Serialization 中的DataContractDataMember 属性来控制JSON 元素如何映射到DTO,但我认为没有任何方法可以映射根元素。有什么方法可以提示ServiceStack.Text JSON Deserializer 当前 JSON 的根元素需要反序列化为这个 Person 对象?

我确定的最佳解决方案是使用属性来解决这个问题。

public class GetPersonResponse
{
    private int _id;
    private string _name;
    private Person _person;

    public int ID { get => _id; set { _id = value; if(_person != null) _person.ID = _id; } }
    public string Name { get => _name; set { _name = value; if (_person != null) _person.Name = _name; } }
    public Person Person { get => _person ?? new Person() { ID = this.ID, Name = this.Name }; set => _person = value; }
}

这是站不住脚的,因为它没有正确封装。 ID 和名称必须保持公开,以便 JSON 反序列化程序能够访问它们。真正的 DTO 也有 30 多个字段,所以这将是一场噩梦。

这样做的目的是使应用程序与客户端库分离,并且只需更新客户端库即可利用新的 API 版本。应用程序将继续访问response.Person,就好像什么都没发生一样。

最终,这归结为ServiceStack.Text 问题。是否可以编写GetPersonResponse1 的版本,除了Person 之外没有公共属性并且没有样板代码,这样以下断言就可以通过了吗?

using ServiceStack;
using System;
using System.Diagnostics;

class Program
{
    static void Main(string[] args)
    {
        string v1 = "{\"id\":1,\"name\":\"Jean-Luc Picard\"}";
        string v2 = "{\"person\":{\"id\":1,\"name\":\"Jean-Luc Picard\"}}";

        GetPersonResponse1 p1 = v1.FromJson<GetPersonResponse1>();
        GetPersonResponse2 p2 = v2.FromJson<GetPersonResponse2>();
        Debug.Assert(p1.Person != null 
            && p2.Person != null 
            && p1.Person.ID == p2.Person.ID 
            && p1.Person.Name == p2.Person.Name);
    }
}

public class GetPersonResponse1
{
    public Person Person { get; set; }
}

public class GetPersonResponse2
{
    public Person Person { get; set; }
}

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
}

更新

电线上的数据形状变化是一个问题,因为我不控制电线 - 它是第 3 方 WebAPI。 WebAPI 实现了 HATEOAS HAL 超媒体类型,因此响应中包含不属于模型的 _links 和其他数据。对于某些端点,它当前返回裸对象。可以想象,如果他们将非模型元数据添加到响应中,那么模型数据将被移动到响应中的 _embedded 元素中。

Application (3rd Party)
  => Client Library (Me)
  => WebAPI (3rd Party)

我的错误是想象应用程序将直接与响应 DTO 一起工作。回想起来,由于多种原因,这没有意义。相反,响应 DTO 应该明确地遵循线路上数据的形状(神话的第一个建议)。然后客户端库应该公开一个抽象层供应用程序与之交互(神话的第二个建议)。

Application
  => Client Library API
  => Client Library Response DTOs
  => WebAPI

我确实计划看看 RestSharp,看看它是否更适合我的用例,但决定先从 ServiceStack 开始。

【问题讨论】:

    标签: servicestack servicestack-text


    【解决方案1】:

    purpose of DTOs(数据传输对象)是使用与 Wire 格式的形状相匹配的类型化模式定义服务合同,因此它们可以与通用序列化程序一起使用,以自动序列化/反序列化有效负载,而无需手动自定义逻辑样板.

    所以我没有关注您为什么要尝试隐藏 DTO 的公共架构以及为什么类型包含嵌入式逻辑,这两者都是 DTO 的反模式,应该是良性的无 impl 数据结构。

    我的第一个建议是更改您的类型,使它们成为 DTO,其中它们的公共架构与它试图反序列化的数据的形状相匹配。

    如果你的类型不是 DTO,并且你想用它来水合数据模型,我会考虑创建一个单独的类型化 DTO 并使用AutoMapping library(或自定义类型映射器扩展方法)来将 Typed DTO 中的数据复制到理想的数据模型中。

    如果您不想从数据模型中维护单独的类型化数据 DTO,您可以使用 generic JSON parser,它将任意数据结构反序列化为松散类型的通用 .NET 数据结构,例如 Dictionary&lt;string,object&gt;

    如果您只是想避免使用公共属性并且乐于使用 公共字段,则可以指定 ServiceStack.Text 的序列化程序来填充公共字段:

    JsConfig.IncludePublicFields = true;
    

    【讨论】:

    • 包含嵌入式逻辑的类型绝对是一种反模式,只是一个关于如何完成的思想实验。
    • 这个想法是让我正在开发的客户端库公开一个 API,如果网络上的数据形状发生变化,该 API 不会改变。我在上面的编辑中解释了为什么这是一个问题,以及我得出的结论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多