@@ -97,7 +97,7 @@ pub fn to_json(doc: &DoclingDocument) -> Value {
9797 let mut b = Builder :: default ( ) ;
9898 let body = b. walk_into ( & doc. nodes , "#/body" ) ;
9999
100- json ! ( {
100+ let mut out = json ! ( {
101101 "schema_name" : "DoclingDocument" ,
102102 "version" : SCHEMA_VERSION ,
103103 "name" : doc. name,
@@ -127,7 +127,23 @@ pub fn to_json(doc: &DoclingDocument) -> Value {
127127 "key_value_items" : [ ] ,
128128 "form_items" : [ ] ,
129129 "pages" : { } ,
130- } )
130+ } ) ;
131+
132+ // docling only emits `field_regions` / `field_items` when a document has
133+ // form fields, and places them just before `pages`. Insert them in that slot
134+ // (re-appending `pages` afterwards, since `preserve_order` keeps insertion
135+ // order) so non-KVP documents' JSON is byte-identical to before.
136+ if !b. field_regions . is_empty ( ) {
137+ if let Some ( obj) = out. as_object_mut ( ) {
138+ let pages = obj. remove ( "pages" ) ;
139+ obj. insert ( "field_regions" . into ( ) , Value :: Array ( b. field_regions ) ) ;
140+ obj. insert ( "field_items" . into ( ) , Value :: Array ( b. field_items ) ) ;
141+ if let Some ( pages) = pages {
142+ obj. insert ( "pages" . into ( ) , pages) ;
143+ }
144+ }
145+ }
146+ out
131147}
132148
133149#[ derive( Default ) ]
@@ -136,6 +152,8 @@ struct Builder {
136152 groups : Vec < Value > ,
137153 tables : Vec < Value > ,
138154 pictures : Vec < Value > ,
155+ field_regions : Vec < Value > ,
156+ field_items : Vec < Value > ,
139157}
140158
141159impl Builder {
@@ -165,11 +183,60 @@ impl Builder {
165183 Some ( self . add_picture ( caption. as_deref ( ) , image. as_ref ( ) , parent) )
166184 }
167185 Node :: Group { label, children } => Some ( self . add_group ( label, children, parent) ) ,
186+ Node :: FieldRegion { items } => Some ( self . add_field_region ( items, parent) ) ,
168187 // Handled by `add_list` in `walk`.
169188 Node :: ListItem { .. } => None ,
170189 }
171190 }
172191
192+ /// A form key-value region: `field_regions/N` holds the region, each field is
193+ /// a `field_items/M` whose children are its `marker` / `field_key` /
194+ /// `field_value` texts (absent parts are simply omitted).
195+ fn add_field_region ( & mut self , items : & [ crate :: FieldItem ] , parent : & str ) -> String {
196+ let self_ref = format ! ( "#/field_regions/{}" , self . field_regions. len( ) ) ;
197+ self . field_regions . push ( Value :: Null ) ;
198+ let region_index = self . field_regions . len ( ) - 1 ;
199+ let mut item_refs = Vec :: new ( ) ;
200+ for item in items {
201+ item_refs. push ( json ! ( { "$ref" : self . add_field_item( item, & self_ref) } ) ) ;
202+ }
203+ self . field_regions [ region_index] = json ! ( {
204+ "self_ref" : self_ref,
205+ "parent" : { "$ref" : parent } ,
206+ "children" : item_refs,
207+ "content_layer" : "body" ,
208+ "label" : "field_region" ,
209+ "prov" : [ ] ,
210+ } ) ;
211+ self_ref
212+ }
213+
214+ fn add_field_item ( & mut self , item : & crate :: FieldItem , parent : & str ) -> String {
215+ let self_ref = format ! ( "#/field_items/{}" , self . field_items. len( ) ) ;
216+ self . field_items . push ( Value :: Null ) ;
217+ let item_index = self . field_items . len ( ) - 1 ;
218+ let mut child_refs = Vec :: new ( ) ;
219+ for ( label, text) in [
220+ ( "marker" , & item. marker ) ,
221+ ( "field_key" , & item. key ) ,
222+ ( "field_value" , & item. value ) ,
223+ ] {
224+ if let Some ( text) = text {
225+ child_refs
226+ . push ( json ! ( { "$ref" : self . add_text( label, text, & self_ref, json!( { } ) ) } ) ) ;
227+ }
228+ }
229+ self . field_items [ item_index] = json ! ( {
230+ "self_ref" : self_ref,
231+ "parent" : { "$ref" : parent } ,
232+ "children" : child_refs,
233+ "content_layer" : "body" ,
234+ "label" : "field_item" ,
235+ "prov" : [ ] ,
236+ } ) ;
237+ self_ref
238+ }
239+
173240 fn add_text ( & mut self , label : & str , text : & str , parent : & str , extra : Value ) -> String {
174241 let self_ref = format ! ( "#/texts/{}" , self . texts. len( ) ) ;
175242 let raw = unescape_text ( text) ;
0 commit comments