C# json使用之Json.NET(3)

以下为json.net的一系列参数设置以及属性设置说明,翻译自官方文档。有错误的地方请指正。接前篇。

Serializing Dates in JSON 在JSON中序列化日期

在JSON中序列化日期

JSON中的DateTimes很难使用。

问题来自JSON规范本身:JSON中没有日期的文字语法。 规范包含对象,数组,字符串,整数和浮点数,但它没有定义日期的标准。

Dates and Json.NET

Json.NET使用的默认格式是ISO 8601标准:“2012-03-19T07:22Z”。

在Json.NET 4.5之前,日期是使用Microsoft格式编写的:“\ / Date(1198908717056)\ /”。 如果要使用此格式,或者希望保持与Microsoft JSON序列化程序或旧版本Json.NET的兼容性,请将DateFormatHandling设置更改为MicrosoftDateFormat。

DateTimeZoneHandling设置可用于在序列化时转换DateTime的DateTimeKind。 例如,将DateTimeZoneHandling设置为Utc以将所有DateTime序列化为UTC日期。 请注意,此设置不会影响DateTimeOffsets。

如果您的日期不符合ISO 8601标准,则DateFormatString设置可用于自定义使用.NET的自定义日期和时间格式语法读取和写入的日期字符串的格式。

DateTime JsonConverters

由于JSON中的日期没有标准,因此在与其他系统交互时可能存在的不同格式的数量是无穷无尽的。 幸运的是,Json.NET有一个处理读写自定义日期的解决方案:JsonConverters。 JsonConverter用于覆盖类型的序列化方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class LogEntry
{
    public string Details { get; set; }
    public DateTime LogDate { get; set; }
}
 
[Test]
public void WriteJsonDates()
{
    LogEntry entry = new LogEntry
    {
        LogDate = new DateTime(2009, 2, 15, 0, 0, 0, DateTimeKind.Utc),
        Details = "Application started."
    };
 
    // default as of Json.NET 4.5
    string isoJson = JsonConvert.SerializeObject(entry);
    // {"Details":"Application started.","LogDate":"2009-02-15T00:00:00Z"}
 
    JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings
    {
        DateFormatHandling = DateFormatHandling.MicrosoftDateFormat
    };
    string microsoftJson = JsonConvert.SerializeObject(entry, microsoftDateFormatSettings);
    // {"Details":"Application started.","LogDate":"\/Date(1234656000000)\/"}
 
    string javascriptJson = JsonConvert.SerializeObject(entry, new JavaScriptDateTimeConverter());
    // {"Details":"Application started.","LogDate":new Date(1234656000000)}
}

只需将您希望使用的JsonConverter传递给Json.NET序列化程序即可。

JavaScriptDateTimeConverter

JavaScriptDateTimeConverter类是Json.NET附带的两个DateTime JsonConverters之一。 此转换器将DateTime序列化为JavaScript Date对象:new Date(1234656000000)。

从技术上讲,这是根据规范无效的JSON,但所有浏览器和一些JSON框架,包括Json.NET,都支持它。

IsoDateTimeConverter

从Json.NET 4.5开始,默认情况下使用ISO 8601格式编写日期,并且不需要使用此转换器。

IsoDateTimeConverter将DateTime序列化为ISO 8601格式的字符串:“2009-02-15T00:00:00Z”

IsoDateTimeConverter类有一个属性DateTimeFormat,用于进一步自定义格式化字符串。

Reducing Serialized JSON Size 减少序列化的JSON大小

减少序列化的JSON大小

将.NET对象序列化为JSON时遇到的一个常见问题是JSON最终包含许多不需要的属性和值。将JSON返回给客户端时,这尤其重要。 更多JSON意味着更多带宽和更慢的网站。

为了解决不需要的JSON问题,Json.NET提供了一系列内置选项来微调从序列化对象写入的内容。

JsonIgnoreAttribute and DataMemberAttribute

默认情况下,Json.NET将在其创建的JSON中包含所有类的公共属性和字段。 将JsonIgnoreAttribute添加到属性会告诉序列化程序始终跳过将其写入JSON结果。

1
2
3
4
5
6
7
8
9
10
11
public class Car
{
    // included in JSON
    public string Model { get; set; }
    public DateTime Year { get; set; }
    public List<string> Features { get; set; }
 
    // ignored
    [JsonIgnore]
    public DateTime LastModified { get; set; }
}

如果一个类有许多属性而你只想序列化它们的一小部分,那么将JsonIgnore添加到所有其他属性将是乏味且容易出错的。 解决此方案的方法是将DataContractAttribute添加到类,将DataMemberAttribute添加到要序列化的属性。 这是选择性序列化 - 只有标记的属性才会被序列化,这与使用JsonIgnoreAttribute的选择退出序列化不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[DataContract]
public class Computer
{
    // included in JSON
    [DataMember]
    public string Name { get; set; }
 
    [DataMember]
    public decimal SalePrice { get; set; }
 
    // ignored
    public string Manufacture { get; set; }
    public int StockCount { get; set; }
    public decimal WholeSalePrice { get; set; }
    public DateTime NextShipmentDate { get; set; }
}

Formatting

由序列化程序编写的JSON,其格式设置为Indented,可生成格式良好,易于阅读的JSON,在开发时非常易于阅读。 另一方面,Formatting.None使JSON结果保持较小,跳过所有不必要的空格和换行符以产生最紧凑和最有效的JSON。

NullValueHandling

NullValueHandling是JsonSerializer上的一个选项,它控制序列化程序如何使用null值处理属性。 通过设置NullValueHandling.Ignore的值,JsonSerializer将跳过写入任何值为null的属性。

1
2
3
4
5
6
7
8
9
public class Movie
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string Classification { get; set; }
    public string Studio { get; set; }
    public DateTime? ReleaseDate { get; set; }
    public List<string> ReleaseCountries { get; set; }
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Movie movie = new Movie();
movie.Name = "Bad Boys III";
movie.Description = "It's no Bad Boys";
 
string included = JsonConvert.SerializeObject(movie,
    Formatting.Indented,
    new JsonSerializerSettings { });
 
// {
//   "Name": "Bad Boys III",
//   "Description": "It's no Bad Boys",
//   "Classification": null,
//   "Studio": null,
//   "ReleaseDate": null,
//   "ReleaseCountries": null
// }
 
string ignored = JsonConvert.SerializeObject(movie,
    Formatting.Indented,
    new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
 
// {
//   "Name": "Bad Boys III",
//   "Description": "It's no Bad Boys"
// }

NullValueHandling也可以使用JsonPropertyAttribute在各个属性上进行自定义。 NullValueHandling的JsonPropertyAttribute值将覆盖该属性的JsonSerializer上的设置。

DefaultValueHandling

DefaultValueHandling是JsonSerializer上的一个选项,它控制序列化程序如何使用默认值处理属性。 设置DefaultValueHandling.Ignore的值将使JsonSerializer跳过将具有默认值的任何属性写入JSON结果。 对于对象引用,这将为null。 对于int和DateTime等值类型,序列化程序将跳过该值类型的默认未初始化值。

Json.NET还允许您使用DefaultValueAttribute自定义单个属性的默认值。 例如,如果名为Department的字符串属性始终返回其默认状态的空字符串,并且您不希望JSON中包含该空字符串,则将具有该值的DefaultValueAttribute放在Department上将意味着Department不再写入JSON,除非 它有价值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Invoice
{
    public string Company { get; set; }
    public decimal Amount { get; set; }
 
    // false is default value of bool
    public bool Paid { get; set; }
    // null is default value of nullable
    public DateTime? PaidDate { get; set; }
 
    // customize default values
    [DefaultValue(30)]
    public int FollowUpDays { get; set; }
 
    [DefaultValue("")]
    public string FollowUpEmailAddress { get; set; }
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Invoice invoice = new Invoice
{
    Company = "Acme Ltd.",
    Amount = 50.0m,
    Paid = false,
    FollowUpDays = 30,
    FollowUpEmailAddress = string.Empty,
    PaidDate = null
};
 
string included = JsonConvert.SerializeObject(invoice,
    Formatting.Indented,
    new JsonSerializerSettings { });
 
// {
//   "Company": "Acme Ltd.",
//   "Amount": 50.0,
//   "Paid": false,
//   "PaidDate": null,
//   "FollowUpDays": 30,
//   "FollowUpEmailAddress": ""
// }
 
string ignored = JsonConvert.SerializeObject(invoice,
    Formatting.Indented,
    new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore });
 
// {
//   "Company": "Acme Ltd.",
//   "Amount": 50.0
// }

也可以使用JsonPropertyAttribute在各个属性上自定义DefaultValueHandling。 DefaultValueHandling的JsonPropertyAttribute值将覆盖该属性的JsonSerializer上的设置。

IContractResolver

为了获得更大的灵活性,IContractResolver提供了一个界面,可以自定义.NET对象如何序列化为JSON的几乎所有方面,包括在运行时更改序列化行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class DynamicContractResolver : DefaultContractResolver
{
    private readonly char _startingWithChar;
 
    public DynamicContractResolver(char startingWithChar)
    {
        _startingWithChar = startingWithChar;
    }
 
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
 
        // only serializer properties that start with the specified character
        properties =
            properties.Where(p => p.PropertyName.StartsWith(_startingWithChar.ToString())).ToList();
 
        return properties;
    }
}
 
public class Book
{
    public string BookName { get; set; }
    public decimal BookPrice { get; set; }
    public string AuthorName { get; set; }
    public int AuthorAge { get; set; }
    public string AuthorCountry { get; set; }
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Book book = new Book
{
    BookName = "The Gathering Storm",
    BookPrice = 16.19m,
    AuthorName = "Brandon Sanderson",
    AuthorAge = 34,
    AuthorCountry = "United States of America"
};
 
string startingWithA = JsonConvert.SerializeObject(book, Formatting.Indented,
    new JsonSerializerSettings { ContractResolver = new DynamicContractResolver('A') });
 
// {
//   "AuthorName": "Brandon Sanderson",
//   "AuthorAge": 34,
//   "AuthorCountry": "United States of America"
// }
 
string startingWithB = JsonConvert.SerializeObject(book, Formatting.Indented,
    new JsonSerializerSettings { ContractResolver = new DynamicContractResolver('B') });
 
// {
//   "BookName": "The Gathering Storm",
//   "BookPrice": 16.19
// }

Deserializing Partial JSON Fragments 反序列化部分JSON片段

反序列化部分JSON片段

通常在处理大型JSON文档时,您只对一小部分信息感兴趣。 当您想要将该JSON片段反序列化为.NET对象时,这种情况可能很烦人,因为您必须为整个JSON结果定义.NET类。

使用Json.NET,很容易解决这个问题。 使用LINQ to JSON,您可以在将它们传递给Json.NET序列化程序之前提取要反序列化的JSON片段。

public class SearchResult {     public string Title { get; set; }     public string Content { get; set; }     public string Url { get; set; } }
1
2
3
4
5
6
public class SearchResult
{
    public string Title { get; set; }
    public string Content { get; set; }
    public string Url { get; set; }
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
string googleSearchText = @"{
  'responseData': {
    'results': [
      {
        'GsearchResultClass': 'GwebSearch',
        'unescapedUrl': 'http://en.wikipedia.org/wiki/Paris_Hilton',
        'url': 'http://en.wikipedia.org/wiki/Paris_Hilton',
        'visibleUrl': 'en.wikipedia.org',
        'cacheUrl': 'http://www.google.com/search?q=cache:TwrPfhd22hYJ:en.wikipedia.org',
        'title': '<b>Paris Hilton</b> - Wikipedia, the free encyclopedia',
        'titleNoFormatting': 'Paris Hilton - Wikipedia, the free encyclopedia',
        'content': '[1] In 2006, she released her debut album...'
      },
      {
        'GsearchResultClass': 'GwebSearch',
        'unescapedUrl': 'http://www.imdb.com/name/nm0385296/',
        'url': 'http://www.imdb.com/name/nm0385296/',
        'visibleUrl': 'www.imdb.com',
        'cacheUrl': 'http://www.google.com/search?q=cache:1i34KkqnsooJ:www.imdb.com',
        'title': '<b>Paris Hilton</b>',
        'titleNoFormatting': 'Paris Hilton',
        'content': 'Self: Zoolander. Socialite <b>Paris Hilton</b>...'
      }
    ],
    'cursor': {
      'pages': [
        {
          'start': '0',
          'label': 1
        },
        {
          'start': '4',
          'label': 2
        },
        {
          'start': '8',
          'label': 3
        },
        {
          'start': '12',
          'label': 4
        }
      ],
      'estimatedResultCount': '59600000',
      'currentPageIndex': 0,
      'moreResultsUrl': 'http://www.google.com/search?oe=utf8&ie=utf8...'
    }
  },
  'responseDetails': null,
  'responseStatus': 200
}";
 
JObject googleSearch = JObject.Parse(googleSearchText);
 
// get JSON result objects into a list
IList<JToken> results = googleSearch["responseData"]["results"].Children().ToList();
 
// serialize JSON results into .NET objects
IList<SearchResult> searchResults = new List<SearchResult>();
foreach (JToken result in results)
{
    // JToken.ToObject is a helper method that uses JsonSerializer internally
    SearchResult searchResult = result.ToObject<SearchResult>();
    searchResults.Add(searchResult);
}
 
// Title = <b>Paris Hilton</b> - Wikipedia, the free encyclopedia
// Content = [1] In 2006, she released her debut album...
// Url = http://en.wikipedia.org/wiki/Paris_Hilton
 
// Title = <b>Paris Hilton</b>
// Content = Self: Zoolander. Socialite <b>Paris Hilton</b>...
// Url = http://www.imdb.com/name/nm0385296/

Conditional Property Serialization 条件属性序列化

条件属性序列化

Json.NET能够通过在类上放置ShouldSerialize方法来有条件地序列化属性。 此功能类似于XmlSerializer ShouldSerialize功能。

ShouldSerialize

要有条件地序列化属性,请添加一个返回与该属性同名的布尔值的方法,然后使用ShouldSerialize作为方法名称的前缀。方法的结果确定属性是否已序列化。 如果该方法返回true,则该属性将被序列化,如果它返回false,则将跳过该属性。

1
2
3
4
5
6
7
8
9
10
11
public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }
 
    public bool ShouldSerializeManager()
    {
        // don't serialize the Manager property if an employee is their own manager
        return (Manager != this);
    }
}
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Employee joe = new Employee();
joe.Name = "Joe Employee";
Employee mike = new Employee();
mike.Name = "Mike Manager";
 
joe.Manager = mike;
 
// mike is his own manager
// ShouldSerialize will skip this property
mike.Manager = mike;
 
string json = JsonConvert.SerializeObject(new[] { joe, mike }, Formatting.Indented);
// [
//   {
//     "Name": "Joe Employee",
//     "Manager": {
//       "Name": "Mike Manager"
//     }
//   },
//   {
//     "Name": "Mike Manager"
//   }
// ]

IContractResolver

也可以使用IContractResolver设置ShouldSerialize。 如果您不想在类上放置ShouldSerialize方法,或者您没有声明类并且无法使用IContractResolver,则有条件地使用IContractResolver序列化属性非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();
 
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
 
        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }
 
        return property;
    }
}

Serialization using ContractResolver 使用ContractResolver进行序列化

使用ContractResolver进行序列化

IContractResolver接口提供了一种方法来自定义JsonSerializer如何将.NET对象序列化和反序列化为JSON,而无需在类上放置属性。

还可以使用IContractResolver设置可以使用属性或方法控制序列化在对象,集合,属性等上设置的任何内容。

DefaultContractResolver

DefaultContractResolver是序列化程序使用的默认解析程序。 它以虚拟方法的形式提供了许多可扩展的途径,可以被覆盖。

CamelCasePropertyNamesContractResolver

CamelCasePropertyNamesContractResolver继承自DefaultContractResolver,只是覆盖要在camelcase中编写的JSON属性名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Product product = new Product
{
    ExpiryDate = new DateTime(2010, 12, 20, 18, 1, 0, DateTimeKind.Utc),
    Name = "Widget",
    Price = 9.99m,
    Sizes = new[] { "Small", "Medium", "Large" }
};
 
string json =
    JsonConvert.SerializeObject(
        product,
        Formatting.Indented,
        new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }
        );
 
//{
//  "name": "Widget",
//  "expiryDate": "2010-12-20T18:01:00Z",
//  "price": 9.99,
//  "sizes": [
//    "Small",
//    "Medium",
//    "Large"
//  ]
//}

Custom IContractResolver Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ConverterContractResolver : DefaultContractResolver
{
    public new static readonly ConverterContractResolver Instance = new ConverterContractResolver();
 
    protected override JsonContract CreateContract(Type objectType)
    {
        JsonContract contract = base.CreateContract(objectType);
 
        // this will only be called once and then cached
        if (objectType == typeof(DateTime) || objectType == typeof(DateTimeOffset))
        {
            contract.Converter = new JavaScriptDateTimeConverter();
        }
 
        return contract;
    }
}

此示例使用IContractResolver为类型设置JsonConverter。 在这里使用契约解析器很有用,因为DateTime不是您自己的类型,并且无法在其上放置JsonConverterAttribute。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ShouldSerializeContractResolver : DefaultContractResolver
{
    public new static readonly ShouldSerializeContractResolver Instance = new ShouldSerializeContractResolver();
 
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
 
        if (property.DeclaringType == typeof(Employee) && property.PropertyName == "Manager")
        {
            property.ShouldSerialize =
                instance =>
                {
                    Employee e = (Employee)instance;
                    return e.Manager != e;
                };
        }
 
        return property;
    }
}

Debugging with Serialization Tracing 使用序列化跟踪进行调试

使用序列化跟踪进行调试

Json.NET序列化程序支持使用ITraceWriter接口进行日志记录和调试。 通过分配跟踪编写器,您可以捕获序列化消息和错误,并在序列化和反序列化JSON时调试Json.NET序列化程序中发生的事情。

ITraceWriter

可以使用JsonSerializerSettings或JsonSerializer上的属性分配跟踪编写器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Staff staff = new Staff();
staff.Name = "Arnie Admin";
staff.Roles = new List<string> { "Administrator" };
staff.StartDate = new DateTime(2000, 12, 12, 12, 12, 12, DateTimeKind.Utc);
 
ITraceWriter traceWriter = new MemoryTraceWriter();
 
JsonConvert.SerializeObject(
    staff,
    new JsonSerializerSettings { TraceWriter = traceWriter, Converters = { new JavaScriptDateTimeConverter() } });
 
Console.WriteLine(traceWriter);
// 2012-11-11T12:08:42.761 Info Started serializing Newtonsoft.Json.Tests.Serialization.Staff. Path ''.
// 2012-11-11T12:08:42.785 Info Started serializing System.DateTime with converter Newtonsoft.Json.Converters.JavaScriptDateTimeConverter. Path 'StartDate'.
// 2012-11-11T12:08:42.791 Info Finished serializing System.DateTime with converter Newtonsoft.Json.Converters.JavaScriptDateTimeConverter. Path 'StartDate'.
// 2012-11-11T12:08:42.797 Info Started serializing System.Collections.Generic.List`1[System.String]. Path 'Roles'.
// 2012-11-11T12:08:42.798 Info Finished serializing System.Collections.Generic.List`1[System.String]. Path 'Roles'.
// 2012-11-11T12:08:42.799 Info Finished serializing Newtonsoft.Json.Tests.Serialization.Staff. Path ''.
// 2013-05-18T21:38:11.255 Verbose Serialized JSON: 
// {
//   "Name": "Arnie Admin",
//   "StartDate": new Date(
//     976623132000
//   ),
//   "Roles": [
//     "Administrator"
//   ]
// }

Json.NET有两个ITraceWriter实现:MemoryTraceWriter,它将消息保存在内存中以便进行简单的调试,如上例所示,以及DiagnosticsTraceWriter,它将消息写入应用程序正在使用的任何System.Diagnostics.TraceListeners。

Custom ITraceWriter

要使用现有的日志记录框架编写消息,只需实现ITraceWriter的自定义版本即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class NLogTraceWriter : ITraceWriter
{
    private static readonly Logger Logger = LogManager.GetLogger("NLogTraceWriter");
 
    public TraceLevel LevelFilter
    {
        // trace all messages. nlog can handle filtering
        get { return TraceLevel.Verbose; }
    }
 
    public void Trace(TraceLevel level, string message, Exception ex)
    {
        LogEventInfo logEvent = new LogEventInfo
        {
            Message = message,
            Level = GetLogLevel(level),
            Exception = ex
        };
 
        // log Json.NET message to NLog
        Logger.Log(logEvent);
    }
 
    private LogLevel GetLogLevel(TraceLevel level)
    {
        switch (level)
        {
            case TraceLevel.Error:
                return LogLevel.Error;
            case TraceLevel.Warning:
                return LogLevel.Warn;
            case TraceLevel.Info:
                return LogLevel.Info;
            case TraceLevel.Off:
                return LogLevel.Off;
            default:
                return LogLevel.Trace;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值