@@ -9,7 +9,7 @@ import $ from 'jquery';
99
1010import { NativeView } from './nativeview' ;
1111
12- import { JSONObject , JSONValue } from '@lumino/coreutils' ;
12+ import { JSONObject , JSONValue , JSONExt } from '@lumino/coreutils' ;
1313
1414import { Message , MessageLoop } from '@lumino/messaging' ;
1515
@@ -29,6 +29,17 @@ import { BufferJSON, Dict } from './utils';
2929
3030import { KernelMessage } from '@jupyterlab/services' ;
3131
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+
3243/**
3344 * Replace model ids with models recursively.
3445 */
@@ -38,24 +49,51 @@ export function unpack_models(
3849) : Promise < WidgetModel | Dict < WidgetModel > | WidgetModel [ ] | any > {
3950 if ( Array . isArray ( value ) ) {
4051 const unpacked : any [ ] = [ ] ;
41- value . forEach ( ( sub_value , key ) => {
52+ for ( const sub_value of value ) {
4253 unpacked . push ( unpack_models ( sub_value , manager ) ) ;
43- } ) ;
54+ }
4455 return Promise . all ( unpacked ) ;
4556 } else if ( value instanceof Object && typeof value !== 'string' ) {
4657 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+ }
5061 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_ ) {
5263 // get_model returns a promise already
5364 return manager ! . get_model ( value . slice ( 10 , value . length ) ) ;
5465 } else {
5566 return Promise . resolve ( value ) ;
5667 }
5768}
5869
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+
5997/**
6098 * Type declaration for general widget serializers.
6199 */
@@ -524,18 +562,26 @@ export class WidgetModel extends Backbone.Model {
524562 * binary array buffers.
525563 */
526564 serialize ( state : Dict < any > ) : JSONObject {
527- const deepcopy =
528- globalThis . structuredClone || ( ( x : any ) => JSON . parse ( JSON . stringify ( x ) ) ) ;
529565 const serializers =
530- ( this . constructor as typeof WidgetModel ) . serializers || { } ;
566+ ( this . constructor as typeof WidgetModel ) . serializers ||
567+ JSONExt . emptyObject ;
531568 for ( const k of Object . keys ( state ) ) {
532569 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 ) ;
535580 } else {
536581 // the default serializer just deep-copies the object
537582 state [ k ] = deepcopy ( state [ k ] ) ;
538583 }
584+
539585 if ( state [ k ] && state [ k ] . toJSON ) {
540586 state [ k ] = state [ k ] . toJSON ( ) ;
541587 }
0 commit comments