Skip to content

Commit 740eda8

Browse files
committed
Allow java.time.Instant to be sent via json
1 parent a16a9b9 commit 740eda8

5 files changed

Lines changed: 103 additions & 14 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.json;
19+
20+
import java.lang.reflect.Type;
21+
import java.math.BigDecimal;
22+
import java.time.Instant;
23+
import java.time.format.DateTimeFormatter;
24+
import java.time.format.DateTimeParseException;
25+
import java.time.temporal.TemporalAccessor;
26+
import java.util.function.BiFunction;
27+
28+
public class InstantCoercer extends TypeCoercer<Instant> {
29+
@Override
30+
public boolean test(Class<?> aClass) {
31+
return Instant.class.isAssignableFrom(aClass);
32+
}
33+
34+
@Override
35+
public BiFunction<JsonInput, PropertySetting, Instant> apply(Type type) {
36+
return (jsonInput, setting) -> {
37+
JsonType token = jsonInput.peek();
38+
39+
if (JsonType.NUMBER.equals(token)) {
40+
return Instant.ofEpochMilli(jsonInput.nextNumber().longValue());
41+
} else if (JsonType.STRING.equals(token)) {
42+
String raw = jsonInput.nextString();
43+
try {
44+
TemporalAccessor parsed = DateTimeFormatter.ISO_INSTANT.parse(raw);
45+
return Instant.from(parsed);
46+
} catch (DateTimeParseException ignored) {
47+
try {
48+
return Instant.ofEpochMilli(new BigDecimal(raw).longValue());
49+
} catch (NumberFormatException e) {
50+
throw new JsonException(raw + " does not look like an Instant");
51+
}
52+
}
53+
}
54+
55+
throw new JsonException("Unable to parse: " + jsonInput.read(Object.class));
56+
};
57+
}
58+
}

java/client/src/org/openqa/selenium/json/JsonOutput.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import java.lang.reflect.Method;
2727
import java.net.URI;
2828
import java.net.URL;
29+
import java.time.Instant;
30+
import java.time.ZoneOffset;
31+
import java.time.format.DateTimeFormatter;
2932
import java.util.ArrayDeque;
3033
import java.util.Collection;
3134
import java.util.Collections;
@@ -127,6 +130,7 @@ public class JsonOutput implements Closeable {
127130
builder.put(Number.class::isAssignableFrom, (obj, depth) -> append(obj.toString()));
128131
builder.put(Boolean.class::isAssignableFrom, (obj, depth) -> append((Boolean) obj ? "true" : "false"));
129132
builder.put(Date.class::isAssignableFrom, (obj, depth) -> append(String.valueOf(MILLISECONDS.toSeconds(((Date) obj).getTime()))));
133+
builder.put(Instant.class::isAssignableFrom, (obj, depth) -> append(asString(DateTimeFormatter.ISO_INSTANT.format((Instant) obj))));
130134
builder.put(Enum.class::isAssignableFrom, (obj, depth) -> append(asString(obj)));
131135
builder.put(File.class::isAssignableFrom, (obj, depth) -> append(((File) obj).getAbsolutePath()));
132136
builder.put(URI.class::isAssignableFrom, (obj, depth) -> append(asString((obj).toString())));

java/client/src/org/openqa/selenium/json/JsonTypeCoercer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ private JsonTypeCoercer(Stream<TypeCoercer<?>> coercers) {
8989
builder.add(new UriCoercer());
9090
builder.add(new UrlCoercer());
9191
builder.add(new UuidCoercer());
92+
builder.add(new InstantCoercer());
9293

9394
// From Selenium
9495
builder.add(new MapCoercer<>(

java/client/test/org/openqa/selenium/json/JsonOutputTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.io.Writer;
5050
import java.net.MalformedURLException;
5151
import java.net.URL;
52+
import java.time.Instant;
5253
import java.util.Arrays;
5354
import java.util.Date;
5455
import java.util.HashMap;
@@ -61,6 +62,7 @@
6162
import java.util.stream.Stream;
6263

6364
import static java.lang.Integer.valueOf;
65+
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
6466
import static java.util.Arrays.asList;
6567
import static java.util.Collections.emptyList;
6668
import static java.util.Collections.emptyMap;
@@ -148,6 +150,19 @@ public void shouldConvertNumbersAsLongs() {
148150
assertThat(o).isInstanceOf(Long.class);
149151
}
150152

153+
@Test
154+
public void shouldConvertAnInstantToEpochMillis() {
155+
Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
156+
String json = convert(now);
157+
158+
System.out.println(json);
159+
160+
String value = JsonParser.parseString(json).getAsString();
161+
// We expect the instant to be an ISO 8601 string
162+
Instant seen = Instant.from(ISO_INSTANT.parse(value));
163+
assertThat(seen).isEqualTo(now);
164+
}
165+
151166
@Test
152167
public void shouldNotChokeWhenCollectionIsNull() {
153168
convert(new BeanWithNullCollection());

java/client/test/org/openqa/selenium/json/JsonTest.java

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,8 @@
1717

1818
package org.openqa.selenium.json;
1919

20-
import static java.util.logging.Level.ALL;
21-
import static java.util.logging.Level.FINE;
22-
import static java.util.logging.Level.OFF;
23-
import static java.util.logging.Level.WARNING;
24-
import static org.assertj.core.api.Assertions.assertThat;
25-
import static org.assertj.core.api.Assertions.byLessThan;
26-
import static org.openqa.selenium.Proxy.ProxyType.PAC;
27-
import static org.openqa.selenium.json.Json.MAP_TYPE;
28-
import static org.openqa.selenium.logging.LogType.BROWSER;
29-
import static org.openqa.selenium.logging.LogType.CLIENT;
30-
import static org.openqa.selenium.logging.LogType.DRIVER;
31-
import static org.openqa.selenium.logging.LogType.SERVER;
32-
3320
import com.google.common.collect.ImmutableMap;
3421
import com.google.common.reflect.TypeToken;
35-
3622
import org.junit.Test;
3723
import org.openqa.selenium.Capabilities;
3824
import org.openqa.selenium.Cookie;
@@ -50,13 +36,27 @@
5036
import org.openqa.selenium.remote.SessionId;
5137

5238
import java.io.StringReader;
39+
import java.time.Instant;
5340
import java.util.Arrays;
5441
import java.util.Collections;
5542
import java.util.Date;
5643
import java.util.List;
5744
import java.util.Map;
5845
import java.util.concurrent.TimeUnit;
5946

47+
import static java.util.logging.Level.ALL;
48+
import static java.util.logging.Level.FINE;
49+
import static java.util.logging.Level.OFF;
50+
import static java.util.logging.Level.WARNING;
51+
import static org.assertj.core.api.Assertions.assertThat;
52+
import static org.assertj.core.api.Assertions.byLessThan;
53+
import static org.openqa.selenium.Proxy.ProxyType.PAC;
54+
import static org.openqa.selenium.json.Json.MAP_TYPE;
55+
import static org.openqa.selenium.logging.LogType.BROWSER;
56+
import static org.openqa.selenium.logging.LogType.CLIENT;
57+
import static org.openqa.selenium.logging.LogType.DRIVER;
58+
import static org.openqa.selenium.logging.LogType.SERVER;
59+
6060
public class JsonTest {
6161

6262
@Test
@@ -216,6 +216,17 @@ public void shouldSetPrimitiveValuesToo() {
216216
assertThat(map.get("magicNumber")).isEqualTo(3L);
217217
}
218218

219+
@Test
220+
public void shouldBeAbleToReadAnInstant() {
221+
// We will lose the nanoseconds
222+
Instant now = Instant.ofEpochMilli(System.currentTimeMillis());
223+
String raw = String.valueOf(now.toEpochMilli());
224+
225+
Instant instant = new Json().toType(raw, Instant.class);
226+
227+
assertThat(instant).isEqualTo(now);
228+
}
229+
219230
@Test
220231
public void shouldPopulateFieldsOnNestedBeans() {
221232
String raw = "{\"name\": \"frank\", \"bean\": {\"value\": \"lots\"}}";

0 commit comments

Comments
 (0)