@@ -543,6 +543,13 @@ private bool CanContainEncryptedValues(Type type)
543543 return property . JsonConverter ;
544544 }
545545
546+ private static bool ConverterHandlesNull ( JsonConverter converter ) =>
547+ // converter.HandleNull is defined only on JsonConverter<T> :/
548+ ( bool ) typeof ( JsonConverter < > )
549+ . MakeGenericType ( converter . Type . NotNull ( ) )
550+ . GetProperty ( nameof ( JsonConverter < object > . HandleNull ) ) !
551+ . GetValue ( converter ) ! ;
552+
546553 private Expression CallPropertyConverterRead ( JsonConverter converter , Type type , Expression reader , Expression jsonOptions , Expression dotvvmState , Expression ? existingValue )
547554 {
548555 Debug . Assert ( reader . Type == typeof ( Utf8JsonReader ) . MakeByRefType ( ) || reader . Type == typeof ( Utf8JsonReader ) , $ "{ reader . Type } != { typeof ( Utf8JsonReader ) . MakeByRefType ( ) } ") ;
@@ -562,7 +569,7 @@ private Expression CallPropertyConverterRead(JsonConverter converter, Type type,
562569 else
563570 {
564571 var read = Call ( Constant ( converter ) , "Read" , Type . EmptyTypes , reader , Constant ( type ) , jsonOptions ) ;
565- if ( read . Type . IsValueType )
572+ if ( ! type . IsAssignableFromNull ( ) || ConverterHandlesNull ( converter ) )
566573 return read ;
567574 else
568575 return Condition (
@@ -586,7 +593,16 @@ private Expression CallPropertyConverterWrite(JsonConverter converter, Expressio
586593 }
587594 else
588595 {
589- return Call ( Constant ( converter ) , nameof ( IDotvvmJsonConverter < object > . Write ) , Type . EmptyTypes , writer , value , jsonOptions ) ;
596+ var write = Call ( Constant ( converter ) , nameof ( IDotvvmJsonConverter < object > . Write ) , Type . EmptyTypes , writer , value , jsonOptions ) ;
597+
598+ if ( ! value . Type . IsAssignableFromNull ( ) || ConverterHandlesNull ( converter ) )
599+ return write ;
600+ else
601+ return IfThenElse (
602+ Equal ( value , Default ( value . Type ) ) ,
603+ Call ( writer , nameof ( Utf8JsonWriter . WriteNullValue ) , Type . EmptyTypes ) ,
604+ write
605+ ) ;
590606 }
591607 }
592608
@@ -674,6 +690,10 @@ private Expression DeserializePropertyValue(ViewModelPropertyMap property, Expre
674690 var type = existingValue . Type ;
675691 Debug . Assert ( type . UnwrapNullableType ( ) == property . Type . UnwrapNullableType ( ) , $ "{ type } != { property . Type } , property: { property . PropertyInfo . DeclaringType } .{ property . Name } ") ;
676692
693+ if ( GetPropertyConverter ( property , type ) is { } customConverter )
694+ {
695+ return CallPropertyConverterRead ( customConverter , type , reader , jsonOptions , dotvvmState , property . Populate ? existingValue : null ) ;
696+ }
677697 if ( ReflectionUtils . IsNullable ( existingValue . Type ) )
678698 {
679699 return Condition (
@@ -682,10 +702,6 @@ private Expression DeserializePropertyValue(ViewModelPropertyMap property, Expre
682702 ifFalse : Convert ( DeserializePropertyValue ( property , reader , existingValue . UnwrapNullable ( throwOnNull : false ) , jsonOptions , dotvvmState ) , type )
683703 ) ;
684704 }
685- if ( GetPropertyConverter ( property , type ) is { } customConverter )
686- {
687- return CallPropertyConverterRead ( customConverter , type , reader , jsonOptions , dotvvmState , property . Populate ? existingValue : null ) ;
688- }
689705
690706 if ( TryDeserializePrimitive ( reader , type ) is { } primitive )
691707 {
@@ -730,6 +746,10 @@ private Expression GetSerializeExpression(ViewModelPropertyMap property, Express
730746 Debug . Assert ( dotvvmState . Type == typeof ( DotvvmSerializationState ) ) ;
731747 Debug . Assert ( value . Type . UnwrapNullableType ( ) == property . Type . UnwrapNullableType ( ) , $ "{ value . Type } != { property . Type } ") ;
732748
749+ if ( GetPropertyConverter ( property , value . Type ) is { } converter )
750+ {
751+ return CallPropertyConverterWrite ( converter , writer , value , jsonOptions , dotvvmState ) ;
752+ }
733753 if ( ReflectionUtils . IsNullableType ( value . Type ) )
734754 {
735755 return IfThenElse (
@@ -738,11 +758,6 @@ private Expression GetSerializeExpression(ViewModelPropertyMap property, Express
738758 Call ( writer , "WriteNullValue" , Type . EmptyTypes )
739759 ) ;
740760 }
741-
742- if ( GetPropertyConverter ( property , value . Type ) is { } converter )
743- {
744- return CallPropertyConverterWrite ( converter , writer , value , jsonOptions , dotvvmState ) ;
745- }
746761 if ( TrySerializePrimitive ( writer , value ) is { } primitive )
747762 {
748763 return primitive ;
0 commit comments