@@ -9,7 +9,7 @@ import $ from 'jquery';
9
9
10
10
import { NativeView } from './nativeview' ;
11
11
12
- import { JSONObject , JSONValue } from '@lumino/coreutils' ;
12
+ import { JSONObject , JSONValue , JSONExt } from '@lumino/coreutils' ;
13
13
14
14
import { Message , MessageLoop } from '@lumino/messaging' ;
15
15
@@ -29,6 +29,17 @@ import { BufferJSON, Dict } from './utils';
29
29
30
30
import { KernelMessage } from '@jupyterlab/services' ;
31
31
32
+ /**
33
+ * The magic key used in the widget graph serialization.
34
+ */
35
+ const IPY_MODEL_ = 'IPY_MODEL_' ;
36
+
37
+ /**
38
+ * A best-effort method for performing deep copies.
39
+ */
40
+ const deepcopy =
41
+ globalThis . structuredClone || ( ( x : any ) => JSON . parse ( JSON . stringify ( x ) ) ) ;
42
+
32
43
/**
33
44
* Replace model ids with models recursively.
34
45
*/
@@ -38,24 +49,51 @@ export function unpack_models(
38
49
) : Promise < WidgetModel | Dict < WidgetModel > | WidgetModel [ ] | any > {
39
50
if ( Array . isArray ( value ) ) {
40
51
const unpacked : any [ ] = [ ] ;
41
- value . forEach ( ( sub_value , key ) => {
52
+ for ( const sub_value of value ) {
42
53
unpacked . push ( unpack_models ( sub_value , manager ) ) ;
43
- } ) ;
54
+ }
44
55
return Promise . all ( unpacked ) ;
45
56
} else if ( value instanceof Object && typeof value !== 'string' ) {
46
57
const unpacked : { [ key : string ] : any } = { } ;
47
- Object . keys ( value ) . forEach ( ( key ) => {
48
- unpacked [ key ] = unpack_models ( value [ key ] , manager ) ;
49
- } ) ;
58
+ for ( const [ key , sub_value ] of Object . entries ( value ) ) {
59
+ unpacked [ key ] = unpack_models ( sub_value , manager ) ;
60
+ }
50
61
return utils . resolvePromisesDict ( unpacked ) ;
51
- } else if ( typeof value === 'string' && value . slice ( 0 , 10 ) === ' IPY_MODEL_' ) {
62
+ } else if ( typeof value === 'string' && value . slice ( 0 , 10 ) === IPY_MODEL_ ) {
52
63
// get_model returns a promise already
53
64
return manager ! . get_model ( value . slice ( 10 , value . length ) ) ;
54
65
} else {
55
66
return Promise . resolve ( value ) ;
56
67
}
57
68
}
58
69
70
+ /** Replace models with ids recursively.
71
+ *
72
+ * If the commonly-used `unpack_models` is given as the `seralize` method,
73
+ * and no `deserialize` is given, this will be used as a default.
74
+ */
75
+ export function pack_models (
76
+ value : WidgetModel | Dict < WidgetModel > | WidgetModel [ ] | any ,
77
+ widget ?: WidgetModel
78
+ ) : any | Dict < unknown > | string | ( Dict < unknown > | string ) [ ] {
79
+ if ( Array . isArray ( value ) ) {
80
+ const model_ids : string [ ] = [ ] ;
81
+ for ( const model of value ) {
82
+ model_ids . push ( pack_models ( model , widget ) ) ;
83
+ }
84
+ return model_ids ;
85
+ } else if ( value instanceof WidgetModel ) {
86
+ return `${ IPY_MODEL_ } ${ value . model_id } ` ;
87
+ } else if ( value instanceof Object && typeof value !== 'string' ) {
88
+ const packed : { [ key : string ] : string } = { } ;
89
+ for ( const [ key , sub_value ] of Object . entries ( value ) ) {
90
+ packed [ key ] = pack_models ( sub_value , widget ) ;
91
+ }
92
+ } else {
93
+ return value ;
94
+ }
95
+ }
96
+
59
97
/**
60
98
* Type declaration for general widget serializers.
61
99
*/
@@ -524,18 +562,26 @@ export class WidgetModel extends Backbone.Model {
524
562
* binary array buffers.
525
563
*/
526
564
serialize ( state : Dict < any > ) : JSONObject {
527
- const deepcopy =
528
- globalThis . structuredClone || ( ( x : any ) => JSON . parse ( JSON . stringify ( x ) ) ) ;
529
565
const serializers =
530
- ( this . constructor as typeof WidgetModel ) . serializers || { } ;
566
+ ( this . constructor as typeof WidgetModel ) . serializers ||
567
+ JSONExt . emptyObject ;
531
568
for ( const k of Object . keys ( state ) ) {
532
569
try {
533
- if ( serializers [ k ] && serializers [ k ] . serialize ) {
534
- state [ k ] = serializers [ k ] . serialize ! ( state [ k ] , this ) ;
570
+ const keySerializers = serializers [ k ] || JSONExt . emptyObject ;
571
+ let { serialize } = keySerializers ;
572
+
573
+ if ( serialize == null && keySerializers . deserialize === unpack_models ) {
574
+ // handle https://github.com/jupyter-widgets/ipywidgets/issues/3735
575
+ serialize = pack_models ;
576
+ }
577
+
578
+ if ( serialize ) {
579
+ state [ k ] = serialize ( state [ k ] , this ) ;
535
580
} else {
536
581
// the default serializer just deep-copies the object
537
582
state [ k ] = deepcopy ( state [ k ] ) ;
538
583
}
584
+
539
585
if ( state [ k ] && state [ k ] . toJSON ) {
540
586
state [ k ] = state [ k ] . toJSON ( ) ;
541
587
}
0 commit comments