Skip to content

Commit 153d560

Browse files
Update boxplot to handle non-numeric values (#6095) (#6097)
Co-authored-by: Steve Gordon <[email protected]>
1 parent 9dc049a commit 153d560

File tree

4 files changed

+135
-7
lines changed

4 files changed

+135
-7
lines changed

src/Nest/Aggregations/AggregateFormatter.cs

+9-7
Original file line numberDiff line numberDiff line change
@@ -251,39 +251,41 @@ private IAggregate GetMatrixStatsAggregate(ref JsonReader reader, IJsonFormatter
251251

252252
private IAggregate GetBoxplotAggregate(ref JsonReader reader, IJsonFormatterResolver formatterResolver, IReadOnlyDictionary<string, object> meta)
253253
{
254+
var nullableDoubleFormatter = new StringDoubleFormatter();
255+
254256
var boxplot = new BoxplotAggregate
255257
{
256-
Min = reader.ReadDouble(),
258+
Min = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver),
257259
Meta = meta
258260
};
259261
reader.ReadNext(); // ,
260262
reader.ReadNext(); // "max"
261263
reader.ReadNext(); // :
262-
boxplot.Max = reader.ReadDouble();
264+
boxplot.Max = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver);
263265
reader.ReadNext(); // ,
264266
reader.ReadNext(); // "q1"
265267
reader.ReadNext(); // :
266-
boxplot.Q1 = reader.ReadDouble();
268+
boxplot.Q1 = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver);
267269
reader.ReadNext(); // ,
268270
reader.ReadNext(); // "q2"
269271
reader.ReadNext(); // :
270-
boxplot.Q2 = reader.ReadDouble();
272+
boxplot.Q2 = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver);
271273
reader.ReadNext(); // ,
272274
reader.ReadNext(); // "q3"
273275
reader.ReadNext(); // :
274-
boxplot.Q3 = reader.ReadDouble();
276+
boxplot.Q3 = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver);
275277

276278
var token = reader.GetCurrentJsonToken();
277279
if (token != JsonToken.EndObject)
278280
{
279281
reader.ReadNext(); // ,
280282
reader.ReadNext(); // "lower"
281283
reader.ReadNext(); // :
282-
boxplot.Lower = reader.ReadDouble();
284+
boxplot.Lower = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver);
283285
reader.ReadNext(); // ,
284286
reader.ReadNext(); // "upper"
285287
reader.ReadNext(); // :
286-
boxplot.Upper = reader.ReadDouble();
288+
boxplot.Upper = nullableDoubleFormatter.Deserialize(ref reader, formatterResolver);
287289
}
288290

289291
return boxplot;

src/Nest/Aggregations/Metric/Boxplot/BoxplotAggregate.cs

+9
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,31 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using Elasticsearch.Net.Utf8Json;
6+
57
namespace Nest
68
{
79
public class BoxplotAggregate : MetricAggregateBase
810
{
11+
[JsonFormatter(typeof(StringDoubleFormatter))]
912
public double Min { get; set; }
1013

14+
[JsonFormatter(typeof(StringDoubleFormatter))]
1115
public double Max { get; set; }
1216

17+
[JsonFormatter(typeof(StringDoubleFormatter))]
1318
public double Q1 { get; set; }
1419

20+
[JsonFormatter(typeof(StringDoubleFormatter))]
1521
public double Q2 { get; set; }
1622

23+
[JsonFormatter(typeof(StringDoubleFormatter))]
1724
public double Q3 { get; set; }
1825

26+
[JsonFormatter(typeof(StringDoubleFormatter))]
1927
public double Lower { get; set; }
2028

29+
[JsonFormatter(typeof(StringDoubleFormatter))]
2130
public double Upper { get; set; }
2231
}
2332
}

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

+42
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ internal class NullableStringDoubleFormatter : IJsonFormatter<double?>
172172
return null;
173173
case JsonToken.String:
174174
var s = reader.ReadString();
175+
176+
if (s.Equals("Infinity", System.StringComparison.Ordinal))
177+
return double.PositiveInfinity;
178+
179+
if (s.Equals("-Infinity", System.StringComparison.Ordinal))
180+
return double.NegativeInfinity;
181+
175182
if (!double.TryParse(s, out var d))
176183
throw new JsonParsingException($"Cannot parse {typeof(double).FullName} from: {s}");
177184

@@ -194,4 +201,39 @@ public void Serialize(ref JsonWriter writer, double? value, IJsonFormatterResolv
194201
writer.WriteDouble(value.Value);
195202
}
196203
}
204+
205+
internal class StringDoubleFormatter : IJsonFormatter<double>
206+
{
207+
public double Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
208+
{
209+
var token = reader.GetCurrentJsonToken();
210+
switch (token)
211+
{
212+
case JsonToken.Null:
213+
throw new JsonParsingException($"Cannot parse non-nullable double value from: {token}.");
214+
215+
case JsonToken.String:
216+
var s = reader.ReadString();
217+
218+
if (s.Equals("Infinity", System.StringComparison.Ordinal))
219+
return double.PositiveInfinity;
220+
221+
if (s.Equals("-Infinity", System.StringComparison.Ordinal))
222+
return double.NegativeInfinity;
223+
224+
if (!double.TryParse(s, out var d))
225+
throw new JsonParsingException($"Cannot parse {typeof(double).FullName} from: {s}");
226+
227+
return d;
228+
229+
case JsonToken.Number:
230+
return reader.ReadDouble();
231+
232+
default:
233+
throw new JsonParsingException($"Cannot parse {typeof(double).FullName} from: {token}");
234+
}
235+
}
236+
237+
public void Serialize(ref JsonWriter writer, double value, IJsonFormatterResolver formatterResolver) => writer.WriteDouble(value);
238+
}
197239
}
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System;
6+
using System.Text;
7+
using Elastic.Elasticsearch.Xunit.XunitPlumbing;
8+
using Elasticsearch.Net;
9+
using FluentAssertions;
10+
using Nest;
11+
12+
namespace Tests.Reproduce
13+
{
14+
public class GitHubIssue6050
15+
{
16+
private static readonly byte[] ResponseBytes = Encoding.UTF8.GetBytes(@"{
17+
""took"" : 1,
18+
""timed_out"" : false,
19+
""_shards"" : {
20+
""total"" : 1,
21+
""successful"" : 1,
22+
""skipped"" : 0,
23+
""failed"" : 0
24+
},
25+
""hits"" : {
26+
""total"" : {
27+
""value"" : 0,
28+
""relation"" : ""eq""
29+
},
30+
""max_score"" : null,
31+
""hits"" : [ ]
32+
},
33+
""aggregations"" : {
34+
""summary_boxplot"" : {
35+
""min"" : ""Infinity"",
36+
""max"" : ""-Infinity"",
37+
""q1"" : ""NaN"",
38+
""q2"" : ""NaN"",
39+
""q3"" : ""NaN"",
40+
""lower"" : ""NaN"",
41+
""upper"" : ""-Infinity""
42+
}
43+
}
44+
}");
45+
46+
[U]
47+
public void BoxplotHandlesNaNValues()
48+
{
49+
var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200"));
50+
var settings = new ConnectionSettings(pool, new InMemoryConnection(ResponseBytes));
51+
var client = new ElasticClient(settings);
52+
53+
var response = client.Search<TestData>(s => s
54+
.Size(0)
55+
.Index("test")
56+
.Aggregations(a => a
57+
.Boxplot("summary_boxplot", mt => mt.Field(f => f.Population))));
58+
59+
var boxplot = response.Aggregations.Boxplot("summary_boxplot");
60+
61+
double.IsNaN(boxplot.Lower).Should().BeTrue();
62+
double.IsNaN(boxplot.Q1).Should().BeTrue();
63+
double.IsNaN(boxplot.Q2).Should().BeTrue();
64+
double.IsNaN(boxplot.Q3).Should().BeTrue();
65+
double.IsInfinity(boxplot.Min).Should().BeTrue();
66+
double.IsNegativeInfinity(boxplot.Max).Should().BeTrue();
67+
double.IsNegativeInfinity(boxplot.Upper).Should().BeTrue();
68+
}
69+
70+
private class TestData
71+
{
72+
public long Population { get; set; }
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)