Skip to content

Commit 210d4fc

Browse files
committed
JSON pointer support
1 parent d01a8db commit 210d4fc

File tree

8 files changed

+502
-78
lines changed

8 files changed

+502
-78
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# 0.2.0
22

3-
- Support for serializing JSON
3+
- Experimental support for serializing JSON
4+
- Support for Sets
5+
- Support for escape sequences in object keys
6+
- Support for JSON pointers (RFC 6901)
7+
- Added crimson.whatIsNext() to get the next value type
48

59
# 0.1.2
610

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,45 @@ enum PlaceType {
132132
}
133133
```
134134

135+
## JSON Pointers
136+
137+
Crimson supports [RFC 6901 JSON pointers](https://www.rfc-editor.org/rfc/rfc6901) to access nested JSON fields:
138+
139+
```json
140+
{
141+
"user": {
142+
"name": "Simon Choi"
143+
},
144+
"tweets": [
145+
{
146+
"text": "Hello World!"
147+
},
148+
{
149+
"text": "Hello Crimson!"
150+
}
151+
]
152+
}
153+
```
154+
155+
We can access the `name` field of the `user` object and the `text` field of the second tweet by providing a JSON pointer:
156+
157+
```dart
158+
@json
159+
class Tweet {
160+
@JsonName('/user/name')
161+
String? userName;
162+
163+
@JsonName('/tweets/1/text')
164+
String? secondTweet;
165+
}
166+
```
167+
168+
JSON pointers are evaluated at compile time and optimized code is generated so there is no runtime overhead.
169+
170+
Fields using JSON pointers are ignored for serialization.
171+
172+
**Important**: You can only use a pointer prefix once in a class. For example, you can't use `/user` and `/user/name` in the same class.
173+
135174
## Custom JSON converters
136175

137176
You can use custom JSON converters to convert between JSON and Dart types using the `@JsonConvert()` annotation:
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import 'dart:convert';
2+
import 'dart:typed_data';
3+
4+
import 'package:crimson/crimson.dart';
5+
import 'package:test/test.dart';
6+
7+
part 'pointer_test.g.dart';
8+
9+
@json
10+
class RfcNoPointer {
11+
@JsonName('foo')
12+
List<String>? foo;
13+
14+
@JsonName('')
15+
int? empty;
16+
17+
@JsonName('a/b')
18+
int? ab;
19+
20+
@JsonName('c%d')
21+
int? cd;
22+
23+
@JsonName('e^f')
24+
int? ef;
25+
26+
@JsonName('g|h')
27+
int? gh;
28+
29+
@JsonName('i\\j')
30+
int? ij;
31+
32+
@JsonName('k\"l')
33+
int? kl;
34+
35+
@JsonName(' ')
36+
int? space;
37+
38+
@JsonName('m~n')
39+
int? mn;
40+
}
41+
42+
@json
43+
class RfcPointer {
44+
@JsonName('/foo')
45+
List<String>? foo;
46+
47+
@JsonName('/')
48+
int? empty;
49+
50+
@JsonName('/a~1b')
51+
int? ab;
52+
53+
@JsonName('/c%d')
54+
int? cd;
55+
56+
@JsonName('/e^f')
57+
int? ef;
58+
59+
@JsonName('/g|h')
60+
int? gh;
61+
62+
@JsonName('/i\\j')
63+
int? ij;
64+
65+
@JsonName('/k\"l')
66+
int? kl;
67+
68+
@JsonName('/ ')
69+
int? space;
70+
71+
@JsonName('/m~0n')
72+
int? mn;
73+
}
74+
75+
@json
76+
class PointerTest {
77+
String? before;
78+
79+
@JsonName('/foo/2/bar')
80+
String? deep1;
81+
82+
@JsonName('/2/foo/1')
83+
String? deep2;
84+
85+
String? after;
86+
}
87+
88+
Uint8List bytes(Map<String, dynamic> json) {
89+
return utf8.encode(jsonEncode(json)) as Uint8List;
90+
}
91+
92+
const rfcJson = {
93+
"foo": ["bar", "baz"],
94+
"": 0,
95+
"a/b": 1,
96+
"c%d": 2,
97+
"e^f": 3,
98+
"g|h": 4,
99+
"i\\j": 5,
100+
"k\"l": 6,
101+
" ": 7,
102+
"m~n": 8
103+
};
104+
105+
void main() {
106+
group('Pointer', () {
107+
test('RFC no pointer', () {
108+
final rfc = Crimson(bytes(rfcJson)).readRfcNoPointer();
109+
expect(rfc.foo, ['bar', 'baz']);
110+
expect(rfc.empty, 0);
111+
expect(rfc.ab, 1);
112+
expect(rfc.cd, 2);
113+
expect(rfc.ef, 3);
114+
expect(rfc.gh, 4);
115+
expect(rfc.ij, 5);
116+
expect(rfc.kl, 6);
117+
expect(rfc.space, 7);
118+
expect(rfc.mn, 8);
119+
});
120+
121+
test('RFC pointer', () {
122+
final rfc = Crimson(bytes(rfcJson)).readRfcPointer();
123+
expect(rfc.foo, ['bar', 'baz']);
124+
expect(rfc.empty, 0);
125+
expect(rfc.ab, 1);
126+
expect(rfc.cd, 2);
127+
expect(rfc.ef, 3);
128+
expect(rfc.gh, 4);
129+
expect(rfc.ij, 5);
130+
expect(rfc.kl, 6);
131+
expect(rfc.space, 7);
132+
expect(rfc.mn, 8);
133+
});
134+
135+
test('missing field', () {
136+
final json = {
137+
'before': '123',
138+
'after': '456',
139+
};
140+
final obj = Crimson(bytes(json)).readPointerTest();
141+
expect(obj.before, '123');
142+
expect(obj.after, '456');
143+
expect(obj.deep1, isNull);
144+
expect(obj.deep2, isNull);
145+
});
146+
147+
test('missing deep field', () {
148+
final json = {
149+
'before': '123',
150+
'foo': [
151+
{},
152+
{},
153+
{'baz': 'a'}
154+
],
155+
'2': {
156+
'moo': ['b', 'c']
157+
},
158+
'after': '456',
159+
};
160+
final obj = Crimson(bytes(json)).readPointerTest();
161+
expect(obj.before, '123');
162+
expect(obj.after, '456');
163+
expect(obj.deep1, isNull);
164+
expect(obj.deep2, isNull);
165+
});
166+
167+
test('wrong field type', () {
168+
final json = {
169+
'before': '123',
170+
'foo': 4,
171+
'2': 'a',
172+
'after': '456',
173+
};
174+
final obj = Crimson(bytes(json)).readPointerTest();
175+
expect(obj.before, '123');
176+
expect(obj.after, '456');
177+
expect(obj.deep1, isNull);
178+
expect(obj.deep2, isNull);
179+
180+
final json2 = {
181+
'before': '123',
182+
'foo': {'2': 'c'},
183+
'2': {'foo': 3.3},
184+
'after': '456',
185+
};
186+
final obj2 = Crimson(bytes(json2)).readPointerTest();
187+
expect(obj2.before, '123');
188+
expect(obj2.after, '456');
189+
expect(obj2.deep1, isNull);
190+
expect(obj2.deep2, isNull);
191+
});
192+
193+
test('empty field', () {
194+
final json = {
195+
'before': '123',
196+
'foo': {},
197+
'2': {},
198+
'after': '456',
199+
};
200+
final obj = Crimson(bytes(json)).readPointerTest();
201+
expect(obj.before, '123');
202+
expect(obj.after, '456');
203+
expect(obj.deep1, isNull);
204+
expect(obj.deep2, isNull);
205+
206+
final json2 = {
207+
'before': '123',
208+
'foo': {'2': []},
209+
'2': {'foo': {}},
210+
'after': '456',
211+
};
212+
final obj2 = Crimson(bytes(json2)).readPointerTest();
213+
expect(obj2.before, '123');
214+
expect(obj2.after, '456');
215+
expect(obj2.deep1, isNull);
216+
expect(obj2.deep2, isNull);
217+
});
218+
219+
test('existing field', () {
220+
final json = {
221+
'before': '123',
222+
'foo': [
223+
'a',
224+
'b',
225+
{'bar': 'c'}
226+
],
227+
'2': {
228+
'foo': ['d', 'e']
229+
},
230+
'after': '456',
231+
};
232+
final obj = Crimson(bytes(json)).readPointerTest();
233+
expect(obj.before, '123');
234+
expect(obj.after, '456');
235+
expect(obj.deep1, 'c');
236+
expect(obj.deep2, 'e');
237+
});
238+
});
239+
}

lib/crimson.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ library crimson;
33
export 'src/annotations.dart' hide JsonIgnore, JsonKebabCase, JsonSnakeCase;
44
export 'src/crimson.dart';
55
export 'src/crimson_writer.dart';
6+
export 'src/json_type.dart';

0 commit comments

Comments
 (0)