@@ -37,6 +37,10 @@ public sealed class ExtractorCodegenTests
3737 // • lines : REQUIRED array-of-objects Line{ sku (required), qty:int }
3838 // • tags : REQUIRED scalar-array string[]
3939 // • scores : REQUIRED scalar-array int[] (kind-typed mirror element regression guard)
40+ // • quantities: REQUIRED scalar-array long[] (StrictArg long .Value unwrap)
41+ // • weights : REQUIRED scalar-array double[] (StrictArg double .Value unwrap)
42+ // • flagsArr: REQUIRED scalar-array bool[] (StrictArg bool .Value unwrap)
43+ // • priority: REQUIRED enum (strict-map `m.priority!` run-proof)
4044 // • note : scalar (not @required)
4145 // • shipTo : single nested Customer (not @required)
4246 private const string Model = """
@@ -54,6 +58,10 @@ public sealed class ExtractorCodegenTests
5458 { "field.object": { "name": "lines", "isArray": true, "@required": true, "@objectRef": "Line" } },
5559 { "field.string": { "name": "tags", "isArray": true, "@required": true } },
5660 { "field.int": { "name": "scores", "isArray": true, "@required": true } },
61+ { "field.long": { "name": "quantities", "isArray": true, "@required": true } },
62+ { "field.double": { "name": "weights", "isArray": true, "@required": true } },
63+ { "field.boolean":{ "name": "flagsArr", "isArray": true, "@required": true } },
64+ { "field.enum": { "name": "priority", "@required": true, "@values": ["LOW", "HIGH"] } },
5765 { "field.string": { "name": "note" } },
5866 { "field.object": { "name": "shipTo", "@objectRef": "Customer" } }
5967 ]}},
@@ -134,6 +142,10 @@ public void Generated_extract_populates_strict_graph_from_dirty_text()
134142 " \" lines\" : [ { \" sku\" : \" A\" , \" qty\" : 2 }, { \" sku\" : \" B\" , \" qty\" : 5 } ]," +
135143 " \" tags\" : [\" x\" , \" y\" ]," +
136144 " \" scores\" : [3, 7]," +
145+ " \" quantities\" : [10, 9000000000]," +
146+ " \" weights\" : [1.5, 2.25]," +
147+ " \" flagsArr\" : [true, false, true]," +
148+ " \" priority\" : \" HIGH\" ," +
137149 " \" shipTo\" : { \" name\" : \" Grace\" }, }\n ```" ;
138150
139151 var order = extract . Invoke ( null , new object ? [ ] { orderMo , dirty } ) ! ;
@@ -162,11 +174,88 @@ public void Generated_extract_populates_strict_graph_from_dirty_text()
162174 var scores = ( ( IEnumerable ) scoresProp . GetValue ( order ) ! ) . Cast < object > ( ) . ToList ( ) ;
163175 Assert . Equal ( new object [ ] { 3 , 7 } , scores . ToArray ( ) ) ;
164176
177+ // required NON-STRING scalar-array (long[]) — proves the StrictArg `x!.Value` long unwrap
178+ // narrows the kind-typed mirror (IReadOnlyList<long?>?) to the strict long element. The
179+ // CS8619-as-error compile gate would have failed had the element type mismatched.
180+ var quantitiesProp = order . GetType ( ) . GetProperty ( "quantities" ) ! ;
181+ Assert . Equal ( typeof ( IReadOnlyList < long > ) , quantitiesProp . PropertyType ) ;
182+ var quantities = ( ( IEnumerable ) quantitiesProp . GetValue ( order ) ! ) . Cast < object > ( ) . ToList ( ) ;
183+ Assert . Equal ( new object [ ] { 10L , 9000000000L } , quantities . ToArray ( ) ) ;
184+ Assert . IsType < long > ( quantities [ 0 ] ) ;
185+
186+ // required NON-STRING scalar-array (double[]) — proves the double `x!.Value` unwrap.
187+ var weightsProp = order . GetType ( ) . GetProperty ( "weights" ) ! ;
188+ Assert . Equal ( typeof ( IReadOnlyList < double > ) , weightsProp . PropertyType ) ;
189+ var weights = ( ( IEnumerable ) weightsProp . GetValue ( order ) ! ) . Cast < object > ( ) . ToList ( ) ;
190+ Assert . Equal ( new object [ ] { 1.5d , 2.25d } , weights . ToArray ( ) ) ;
191+ Assert . IsType < double > ( weights [ 0 ] ) ;
192+
193+ // required NON-STRING scalar-array (bool[]) — proves the bool `x!.Value` unwrap.
194+ var flagsProp = order . GetType ( ) . GetProperty ( "flagsArr" ) ! ;
195+ Assert . Equal ( typeof ( IReadOnlyList < bool > ) , flagsProp . PropertyType ) ;
196+ var flags = ( ( IEnumerable ) flagsProp . GetValue ( order ) ! ) . Cast < object > ( ) . ToList ( ) ;
197+ Assert . Equal ( new object [ ] { true , false , true } , flags . ToArray ( ) ) ;
198+ Assert . IsType < bool > ( flags [ 0 ] ) ;
199+
200+ // required ENUM through the strict tier — the StrictArg enum branch (`m.priority!`,
201+ // string-backed) populates the strict record from dirty input. NOTE the strict property
202+ // type is `object` (not `string`): PayloadCodegen's scalar map has no enum entry, so an enum
203+ // field falls through to the `object` default — but the value is the string-backed member.
204+ var priorityProp = order . GetType ( ) . GetProperty ( "priority" ) ! ;
205+ Assert . Equal ( typeof ( object ) , priorityProp . PropertyType ) ;
206+ Assert . Equal ( "HIGH" , priorityProp . GetValue ( order ) ) ;
207+
165208 // non-@required single nested, present -> populates.
166209 var shipTo = order . GetType ( ) . GetProperty ( "shipTo" ) ! . GetValue ( order ) ! ;
167210 Assert . Equal ( "Grace" , shipTo . GetType ( ) . GetProperty ( "name" ) ! . GetValue ( shipTo ) ) ;
168211 }
169212
213+ [ Fact ]
214+ public void Generated_extract_opts_overload_populates_same_strict_graph ( )
215+ {
216+ var root = Load ( Model ) ;
217+ var asm = Compile ( root ) ;
218+
219+ var extractorType = asm . GetType ( "Acme.Generated.OrderExtractor" ) ! ;
220+ var optsType = typeof ( MetaObjects . Render . Extract . ExtractOptions ) ;
221+
222+ // The 3-arg overload: Extract(MetaObject, string, ExtractOptions).
223+ var extractWithOpts = extractorType . GetMethod ( "Extract" ,
224+ new [ ] { typeof ( MetaObject ) , typeof ( string ) , optsType } ) ! ;
225+ Assert . NotNull ( extractWithOpts ) ;
226+
227+ MetaObject orderMo = root . FindObject ( "Order" ) ! ;
228+
229+ // NOTE: shipTo is included because PayloadCodegen types EVERY field (even non-@required ones)
230+ // as `required` non-nullable, so the strict mapper maps shipTo as required — omitting it would
231+ // pass null to ToStrict_Customer (the documented C#-specific optionality, see the file header).
232+ const string dirty =
233+ "Here:\n ```json\n " +
234+ "{ \" orderId\" : \" A-200\" ," +
235+ " \" customer\" : { \" name\" : \" Ada\" }," +
236+ " \" lines\" : [ { \" sku\" : \" A\" , \" qty\" : 3 } ]," +
237+ " \" tags\" : [\" x\" ]," +
238+ " \" scores\" : [9]," +
239+ " \" quantities\" : [42]," +
240+ " \" weights\" : [3.5]," +
241+ " \" flagsArr\" : [false]," +
242+ " \" priority\" : \" HIGH\" ," +
243+ " \" shipTo\" : { \" name\" : \" Grace\" } }\n ```" ;
244+
245+ var opts = optsType . GetMethod ( "Defaults" ) ! . Invoke ( null , null ) ;
246+ var order = extractWithOpts . Invoke ( null , new object ? [ ] { orderMo , dirty , opts } ) ! ;
247+
248+ // Same populated strict graph as the 2-arg path, via the opts overload.
249+ Assert . Equal ( "A-200" , order . GetType ( ) . GetProperty ( "orderId" ) ! . GetValue ( order ) ) ;
250+ var customer = order . GetType ( ) . GetProperty ( "customer" ) ! . GetValue ( order ) ! ;
251+ Assert . Equal ( "Ada" , customer . GetType ( ) . GetProperty ( "name" ) ! . GetValue ( customer ) ) ;
252+
253+ var quantities = ( ( IEnumerable ) order . GetType ( ) . GetProperty ( "quantities" ) ! . GetValue ( order ) ! )
254+ . Cast < object > ( ) . ToList ( ) ;
255+ Assert . Equal ( new object [ ] { 42L } , quantities . ToArray ( ) ) ;
256+ Assert . Equal ( "HIGH" , order . GetType ( ) . GetProperty ( "priority" ) ! . GetValue ( order ) ) ;
257+ }
258+
170259 [ Fact ]
171260 public void Generated_extract_throws_on_lost_required ( )
172261 {
@@ -195,7 +284,8 @@ public void Re_exposed_extract_never_throws_on_clean_input()
195284
196285 const string clean =
197286 "{ \" orderId\" : \" A-7\" , \" customer\" : { \" name\" : \" Ada\" }," +
198- " \" lines\" : [ { \" sku\" : \" A\" , \" qty\" : 1 } ], \" tags\" : [\" a\" ], \" scores\" : [1] }" ;
287+ " \" lines\" : [ { \" sku\" : \" A\" , \" qty\" : 1 } ], \" tags\" : [\" a\" ], \" scores\" : [1]," +
288+ " \" quantities\" : [1], \" weights\" : [1.0], \" flagsArr\" : [true], \" priority\" : \" LOW\" }" ;
199289
200290 var result = extractLenient . Invoke ( null , new object ? [ ] { orderMo , clean } ) ! ;
201291 var report = result . GetType ( ) . GetProperty ( "Report" ) ! . GetValue ( result ) ! ;
0 commit comments