Skip to content

Commit da9836e

Browse files
authored
Support StringTimeSpanAttribute (#4052)
This commit supports StringTimeSpan attribute on TimeSpan and TimeSpan? properties. It also brings in upstream PR to fix ISO8601TimeSpanFormatter deserialization: neuecc/Utf8Json#101 Fixes #3857
1 parent 084dd2b commit da9836e

File tree

7 files changed

+116
-22
lines changed

7 files changed

+116
-22
lines changed

src/Elasticsearch.Net/Utf8Json/Formatters/DateTimeFormatter.cs

+29-4
Original file line numberDiff line numberDiff line change
@@ -862,9 +862,34 @@ public void Serialize(ref JsonWriter writer, TimeSpan value, IJsonFormatterResol
862862
}
863863
writer.WriteInt32(second);
864864

865-
if (nanosecond != 0)
865+
if (nanosecond != 0)
866866
{
867867
writer.WriteRawUnsafe((byte)'.');
868+
if (nanosecond < 1000000)
869+
{
870+
writer.WriteRawUnsafe((byte) '0');
871+
if (nanosecond < 100000)
872+
{
873+
writer.WriteRawUnsafe((byte) '0');
874+
if (nanosecond < 10000)
875+
{
876+
writer.WriteRawUnsafe((byte) '0');
877+
if (nanosecond < 1000)
878+
{
879+
writer.WriteRawUnsafe((byte) '0');
880+
if (nanosecond < 100)
881+
{
882+
writer.WriteRawUnsafe((byte) '0');
883+
if (nanosecond < 10)
884+
{
885+
writer.WriteRawUnsafe((byte) '0');
886+
}
887+
}
888+
}
889+
}
890+
}
891+
}
892+
868893
writer.WriteInt64(nanosecond);
869894
}
870895

@@ -877,14 +902,14 @@ public TimeSpan Deserialize(ref JsonReader reader, IJsonFormatterResolver format
877902
var array = str.Array;
878903
var i = str.Offset;
879904
var len = str.Count;
880-
var to = str.Offset + str.Count;
905+
var to = str.Offset + len;
881906

882907
// check day exists
883908
bool hasDay = false;
884909
{
885910
bool foundDot = false;
886911
bool foundColon = false;
887-
for (int j = i; j < str.Count; j++)
912+
for (int j = i; j < to; j++)
888913
{
889914
if (array[j] == '.')
890915
{
@@ -989,7 +1014,7 @@ public TimeSpan Deserialize(ref JsonReader reader, IJsonFormatterResolver format
9891014
: ts.Add(tk);
9901015

9911016
ERROR:
992-
throw new InvalidOperationException("invalid datetime format. value:" + StringEncoding.UTF8.GetString(str.Array, str.Offset, str.Count));
1017+
throw new InvalidOperationException("invalid TimeSpan format. value:" + StringEncoding.UTF8.GetString(str.Array, str.Offset, str.Count));
9931018
}
9941019
}
9951020
}

src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/NestFormatterResolver.cs

+16-5
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ internal sealed class InnerResolver : IJsonFormatterResolver
4141
{
4242
new QueryContainerCollectionFormatter(),
4343
new SimpleQueryStringFlagsFormatter(),
44-
// TODO: condition on TimeSpanToStringFormatter and NullableTimeSpanToStringFormatter to only take effect when StringTimeSpanAttribute is not present.
45-
new TimeSpanToStringFormatter(),
46-
new NullableTimeSpanToStringFormatter(),
44+
new TimeSpanTicksFormatter(),
45+
new NullableTimeSpanTicksFormatter(),
4746
new JsonNetCompatibleUriFormatter(),
4847
new GeoOrientationFormatter(),
4948
new NullableGeoOrientationFormatter(),
@@ -89,9 +88,7 @@ private JsonProperty GetMapping(MemberInfo member)
8988
propertyMapping = ElasticsearchPropertyAttributeBase.From(member);
9089

9190
var serializerMapping = _settings.PropertyMappingProvider?.CreatePropertyMapping(member);
92-
9391
var nameOverride = propertyMapping?.Name ?? serializerMapping?.Name;
94-
9592
var property = new JsonProperty(nameOverride);
9693

9794
var overrideIgnore = propertyMapping?.Ignore ?? serializerMapping?.Ignore;
@@ -103,6 +100,20 @@ private JsonProperty GetMapping(MemberInfo member)
103100

104101
if (member.GetCustomAttribute<StringEnumAttribute>() != null)
105102
CreateEnumFormatterForProperty(member, property);
103+
else if (member.GetCustomAttribute<StringTimeSpanAttribute>() != null)
104+
{
105+
switch (member)
106+
{
107+
case PropertyInfo propertyInfo:
108+
property.JsonFormatter =
109+
BuiltinResolver.BuiltinResolverGetFormatterHelper.GetFormatter(propertyInfo.PropertyType);
110+
break;
111+
case FieldInfo fieldInfo:
112+
property.JsonFormatter =
113+
BuiltinResolver.BuiltinResolverGetFormatterHelper.GetFormatter(fieldInfo.FieldType);
114+
break;
115+
}
116+
}
106117

107118
return property;
108119
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Nest
55
{
6-
internal class NullableTimeSpanToStringFormatter : IJsonFormatter<TimeSpan?>
6+
internal class NullableTimeSpanTicksFormatter : IJsonFormatter<TimeSpan?>
77
{
88
public TimeSpan? Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
99
{

src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/TimeSpanToStringFormatter.cs renamed to src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/TimeSpanTicksFormatter.cs

+1-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Nest
55
{
6-
internal class TimeSpanToStringFormatter : IJsonFormatter<TimeSpan>
6+
internal class TimeSpanTicksFormatter : IJsonFormatter<TimeSpan>
77
{
88
public TimeSpan Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
99
{
@@ -18,10 +18,4 @@ public TimeSpan Deserialize(ref JsonReader reader, IJsonFormatterResolver format
1818

1919
public void Serialize(ref JsonWriter writer, TimeSpan value, IJsonFormatterResolver formatterResolver) => writer.WriteInt64(value.Ticks);
2020
}
21-
22-
//TODO: hook up in NestFormatterResolver to return TimeSpanToStringFormatter when StringTimeSpanAttribute is *not* specified on T
23-
internal class TimeSpanToStringFormatterResolver : IJsonFormatterResolver
24-
{
25-
public IJsonFormatter<T> GetFormatter<T>() => null;
26-
}
2721
}

src/Tests/Tests.Reproduce/DateSerialization.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Elasticsearch.Net;
66
using FluentAssertions;
77
using Nest;
8+
using Nest.JsonNetSerializer;
89

910
namespace Tests.Reproduce
1011
{
@@ -13,21 +14,24 @@ public class DateSerialization
1314
[U]
1415
public void ShouldRoundtripDateTimeAndDateTimeOffsetWithSameKindAndOffset()
1516
{
16-
var dates = new Dates {
17+
var dates = new Dates
18+
{
1719
DateTimeUtcKind = new DateTime(2016, 1, 1, 1, 1, 1, DateTimeKind.Utc),
1820
DateTimeOffset = new DateTimeOffset(1999, 1, 1, 1, 1, 1, 1, TimeSpan.FromHours(5)),
1921
DateTimeOffsetUtc = new DateTimeOffset(1999, 1, 1, 1, 1, 1, 1, TimeSpan.Zero)
2022
};
2123

2224
var client = new ElasticClient();
23-
var serializedDates = client.SourceSerializer.SerializeToString(dates, client.ConnectionSettings.MemoryStreamFactory, SerializationFormatting.None);
25+
var serializedDates =
26+
client.SourceSerializer.SerializeToString(dates, client.ConnectionSettings.MemoryStreamFactory, SerializationFormatting.None);
2427

2528
serializedDates.Should()
2629
.Contain("2016-01-01T01:01:01Z")
2730
.And.Contain("1999-01-01T01:01:01.0010000+05:00")
2831
.And.Contain("1999-01-01T01:01:01.0010000+00:00");
2932

30-
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(serializedDates))) {
33+
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(serializedDates)))
34+
{
3135
var deserializedDates = client.RequestResponseSerializer.Deserialize<Dates>(stream);
3236

3337
deserializedDates.DateTimeUtcKind.Should().Be(dates.DateTimeUtcKind);
@@ -42,6 +46,7 @@ public void ShouldRoundtripDateTimeAndDateTimeOffsetWithSameKindAndOffset()
4246
deserializedDates.DateTimeOffsetUtc.Date.Kind.Should().Be(dates.DateTimeOffsetUtc.Date.Kind);
4347
}
4448
}
49+
4550
[U]
4651
public void ShouldRoundtripDateTimeAndDateTimeOffsetWithSameKindAndOffsetNewtonsoft()
4752
{
@@ -52,9 +57,10 @@ public void ShouldRoundtripDateTimeAndDateTimeOffsetWithSameKindAndOffsetNewtons
5257
DateTimeOffsetUtc = new DateTimeOffset(1999, 1, 1, 1, 1, 1, 1, TimeSpan.Zero)
5358
};
5459

55-
var sett = new ConnectionSettings(new SingleNodeConnectionPool(new Uri("http://localhost:9200")), Nest.JsonNetSerializer.JsonNetSerializer.Default);
60+
var sett = new ConnectionSettings(new SingleNodeConnectionPool(new Uri("http://localhost:9200")), JsonNetSerializer.Default);
5661
var client = new ElasticClient(sett);
57-
var serializedDates = client.SourceSerializer.SerializeToString(dates, client.ConnectionSettings.MemoryStreamFactory, SerializationFormatting.None);
62+
var serializedDates =
63+
client.SourceSerializer.SerializeToString(dates, client.ConnectionSettings.MemoryStreamFactory, SerializationFormatting.None);
5864

5965
serializedDates.Should()
6066
.Contain("2016-01-01T01:01:01Z")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Text;
3+
using Elastic.Xunit.XunitPlumbing;
4+
using Elasticsearch.Net;
5+
using FluentAssertions;
6+
using Nest;
7+
8+
namespace Tests.Reproduce
9+
{
10+
public class TimeSpanSerialization
11+
{
12+
[U]
13+
public void SerializeTimeSpansAsTicksAndStrings()
14+
{
15+
var timeSpans = new TimeSpans(TimeSpan.FromSeconds(902312));
16+
var client = new ElasticClient();
17+
18+
var json = client.RequestResponseSerializer.SerializeToString(timeSpans);
19+
20+
json.Should()
21+
.Be("{\"default\":9023120000000,\"defaultNullable\":9023120000000,\"string\":\"10.10:38:32\",\"stringNullable\":\"10.10:38:32\"}");
22+
23+
TimeSpans deserialized;
24+
using (var stream = client.ConnectionSettings.MemoryStreamFactory.Create(Encoding.UTF8.GetBytes(json)))
25+
deserialized = client.RequestResponseSerializer.Deserialize<TimeSpans>(stream);
26+
27+
timeSpans.Default.Should().Be(deserialized.Default);
28+
timeSpans.DefaultNullable.Should().Be(deserialized.DefaultNullable);
29+
timeSpans.String.Should().Be(deserialized.String);
30+
timeSpans.StringNullable.Should().Be(deserialized.StringNullable);
31+
}
32+
33+
private class TimeSpans
34+
{
35+
public TimeSpans(TimeSpan timeSpan)
36+
{
37+
Default = timeSpan;
38+
DefaultNullable = timeSpan;
39+
String = timeSpan;
40+
StringNullable = timeSpan;
41+
}
42+
43+
public TimeSpans()
44+
{
45+
}
46+
47+
public TimeSpan Default { get; set; }
48+
49+
public TimeSpan DefaultNullable { get; set; }
50+
51+
[StringTimeSpan]
52+
public TimeSpan String { get; set; }
53+
54+
[StringTimeSpan]
55+
public TimeSpan? StringNullable { get; set; }
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)