Skip to content

Commit 1266373

Browse files
vkryachkoBrian Chen
authored and
Brian Chen
committed
Implement encoder context nesting. (#1018)
* Implement encoder context nesting. * Update api.txt * Fix double-nested issue.
1 parent 797cb7b commit 1266373

File tree

4 files changed

+127
-0
lines changed

4 files changed

+127
-0
lines changed

encoders/firebase-encoders/api.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package com.google.firebase.encoders {
2020
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, int) throws com.google.firebase.encoders.EncodingException;
2121
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, long) throws com.google.firebase.encoders.EncodingException;
2222
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, boolean) throws com.google.firebase.encoders.EncodingException;
23+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext nested(@NonNull String);
2324
}
2425

2526
public interface ValueEncoder<T> {

encoders/firebase-encoders/src/main/java/com/google/firebase/encoders/ObjectEncoderContext.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,28 @@ ObjectEncoderContext add(@NonNull String name, double value)
6868
@NonNull
6969
ObjectEncoderContext add(@NonNull String name, boolean value)
7070
throws IOException, EncodingException;
71+
72+
/**
73+
* Begin a nested JSON object.
74+
*
75+
* <p>Unlike {@code add()} methods, this method returns a new "child" context that's used to
76+
* populate the nested JSON object. This context can only be used until the parent context is
77+
* mutated by calls to {@code add()} or {@code nested()}, violating this will result in a {@link
78+
* IllegalStateException}.
79+
*
80+
* <p>Nesting can be arbitrarily deep.
81+
*
82+
* <p>Example:
83+
*
84+
* <pre>{@code
85+
* ctx.add("key", "value");
86+
* ObjectEncoderContext nested = ctx.nested("nested");
87+
* nested.add("key", "value");
88+
*
89+
* // After this call the above nested context is invalid.
90+
* ctx.add("anotherKey", 1);
91+
* }</pre>
92+
*/
93+
@NonNull
94+
ObjectEncoderContext nested(@NonNull String name) throws IOException;
7195
}

encoders/firebase-encoders/src/main/java/com/google/firebase/encoders/json/JsonValueObjectEncoderContext.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
final class JsonValueObjectEncoderContext implements ObjectEncoderContext, ValueEncoderContext {
3232

33+
private JsonValueObjectEncoderContext childContext = null;
34+
private boolean active = true;
3335
private final JsonWriter jsonWriter;
3436
private final Map<Class<?>, ObjectEncoder<?>> objectEncoders;
3537
private final Map<Class<?>, ValueEncoder<?>> valueEncoders;
@@ -43,10 +45,17 @@ final class JsonValueObjectEncoderContext implements ObjectEncoderContext, Value
4345
this.valueEncoders = valueEncoders;
4446
}
4547

48+
private JsonValueObjectEncoderContext(JsonValueObjectEncoderContext anotherContext) {
49+
this.jsonWriter = anotherContext.jsonWriter;
50+
this.objectEncoders = anotherContext.objectEncoders;
51+
this.valueEncoders = anotherContext.valueEncoders;
52+
}
53+
4654
@NonNull
4755
@Override
4856
public JsonValueObjectEncoderContext add(@NonNull String name, @Nullable Object o)
4957
throws IOException, EncodingException {
58+
maybeUnNest();
5059
jsonWriter.name(name);
5160
if (o == null) {
5261
jsonWriter.nullValue();
@@ -59,6 +68,7 @@ public JsonValueObjectEncoderContext add(@NonNull String name, @Nullable Object
5968
@Override
6069
public JsonValueObjectEncoderContext add(@NonNull String name, double value)
6170
throws IOException, EncodingException {
71+
maybeUnNest();
6272
jsonWriter.name(name);
6373
return add(value);
6474
}
@@ -67,6 +77,7 @@ public JsonValueObjectEncoderContext add(@NonNull String name, double value)
6777
@Override
6878
public JsonValueObjectEncoderContext add(@NonNull String name, int value)
6979
throws IOException, EncodingException {
80+
maybeUnNest();
7081
jsonWriter.name(name);
7182
return add(value);
7283
}
@@ -75,6 +86,7 @@ public JsonValueObjectEncoderContext add(@NonNull String name, int value)
7586
@Override
7687
public JsonValueObjectEncoderContext add(@NonNull String name, long value)
7788
throws IOException, EncodingException {
89+
maybeUnNest();
7890
jsonWriter.name(name);
7991
return add(value);
8092
}
@@ -83,42 +95,58 @@ public JsonValueObjectEncoderContext add(@NonNull String name, long value)
8395
@Override
8496
public JsonValueObjectEncoderContext add(@NonNull String name, boolean value)
8597
throws IOException, EncodingException {
98+
maybeUnNest();
8699
jsonWriter.name(name);
87100
return add(value);
88101
}
89102

103+
@NonNull
104+
@Override
105+
public ObjectEncoderContext nested(@NonNull String name) throws IOException {
106+
maybeUnNest();
107+
childContext = new JsonValueObjectEncoderContext(this);
108+
jsonWriter.name(name);
109+
jsonWriter.beginObject();
110+
return childContext;
111+
}
112+
90113
@NonNull
91114
@Override
92115
public JsonValueObjectEncoderContext add(@Nullable String value)
93116
throws IOException, EncodingException {
117+
maybeUnNest();
94118
jsonWriter.value(value);
95119
return this;
96120
}
97121

98122
@NonNull
99123
@Override
100124
public JsonValueObjectEncoderContext add(double value) throws IOException, EncodingException {
125+
maybeUnNest();
101126
jsonWriter.value(value);
102127
return this;
103128
}
104129

105130
@NonNull
106131
@Override
107132
public JsonValueObjectEncoderContext add(int value) throws IOException, EncodingException {
133+
maybeUnNest();
108134
jsonWriter.value(value);
109135
return this;
110136
}
111137

112138
@NonNull
113139
@Override
114140
public JsonValueObjectEncoderContext add(long value) throws IOException, EncodingException {
141+
maybeUnNest();
115142
jsonWriter.value(value);
116143
return this;
117144
}
118145

119146
@NonNull
120147
@Override
121148
public JsonValueObjectEncoderContext add(boolean value) throws IOException, EncodingException {
149+
maybeUnNest();
122150
jsonWriter.value(value);
123151
return this;
124152
}
@@ -127,6 +155,7 @@ public JsonValueObjectEncoderContext add(boolean value) throws IOException, Enco
127155
@Override
128156
public JsonValueObjectEncoderContext add(@Nullable byte[] bytes)
129157
throws IOException, EncodingException {
158+
maybeUnNest();
130159
if (bytes == null) {
131160
jsonWriter.nullValue();
132161
} else {
@@ -237,6 +266,20 @@ JsonValueObjectEncoderContext add(@Nullable Object o) throws IOException, Encodi
237266
}
238267

239268
void close() throws IOException {
269+
maybeUnNest();
240270
jsonWriter.flush();
241271
}
272+
273+
private void maybeUnNest() throws IOException {
274+
if (!active) {
275+
throw new IllegalStateException(
276+
"Parent context used since this context was created. Cannot use this context anymore.");
277+
}
278+
if (childContext != null) {
279+
childContext.maybeUnNest();
280+
childContext.active = false;
281+
childContext = null;
282+
jsonWriter.endObject();
283+
}
284+
}
242285
}

encoders/firebase-encoders/src/test/java/com/google/firebase/encoders/json/JsonValueObjectEncoderContextTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.firebase.encoders.DataEncoder;
2626
import com.google.firebase.encoders.EncodingException;
2727
import com.google.firebase.encoders.ObjectEncoder;
28+
import com.google.firebase.encoders.ObjectEncoderContext;
2829
import com.google.firebase.encoders.ValueEncoder;
2930
import java.io.IOException;
3031
import java.io.Writer;
@@ -386,4 +387,62 @@ public void testEncoderError() throws IOException, EncodingException {
386387
.build()
387388
.encode(DummyClass.INSTANCE, mockWriter));
388389
}
390+
391+
@Test
392+
public void testNested_whenUsedCorrectly_shouldProduceNestedJson() throws EncodingException {
393+
ObjectEncoder<DummyClass> objectEncoder =
394+
(o, ctx) -> {
395+
ctx.add("name", "value");
396+
ctx.nested("nested1").add("key1", "value1");
397+
ctx.add("after1", true);
398+
ctx.nested("nested2").add("key2", "value2");
399+
ctx.add("after2", true);
400+
};
401+
402+
String result =
403+
new JsonDataEncoderBuilder()
404+
.registerEncoder(DummyClass.class, objectEncoder)
405+
.build()
406+
.encode(DummyClass.INSTANCE);
407+
408+
assertThat(result)
409+
.isEqualTo(
410+
"{\"name\":\"value\",\"nested1\":{\"key1\":\"value1\"},"
411+
+ "\"after1\":true,\"nested2\":{\"key2\":\"value2\"},\"after2\":true}");
412+
}
413+
414+
@Test
415+
public void testTwiceNested_whenUsedCorrectly_shouldProduceNestedJson() throws EncodingException {
416+
ObjectEncoder<DummyClass> objectEncoder =
417+
(o, ctx) -> {
418+
ctx.add("name", "value");
419+
ctx.nested("nested1").add("key1", "value1").nested("nested2");
420+
ctx.add("after1", true);
421+
};
422+
423+
String result =
424+
new JsonDataEncoderBuilder()
425+
.registerEncoder(DummyClass.class, objectEncoder)
426+
.build()
427+
.encode(DummyClass.INSTANCE);
428+
429+
assertThat(result)
430+
.isEqualTo(
431+
"{\"name\":\"value\",\"nested1\":{\"key1\":\"value1\",\"nested2\":{}},\"after1\":true}");
432+
}
433+
434+
@Test
435+
public void testNested_whenUsedAfterParent_shouldThrow() {
436+
ObjectEncoder<DummyClass> objectEncoder =
437+
(o, ctx) -> {
438+
ObjectEncoderContext nested = ctx.nested("nested1");
439+
ctx.add("after1", true);
440+
nested.add("hello", "world");
441+
};
442+
443+
DataEncoder encoder =
444+
new JsonDataEncoderBuilder().registerEncoder(DummyClass.class, objectEncoder).build();
445+
446+
assertThrows(IllegalStateException.class, () -> encoder.encode(DummyClass.INSTANCE));
447+
}
389448
}

0 commit comments

Comments
 (0)