public HandlerForTypeMapping(JsonCodeGenHandler mapping, NullableTypeTree t)
 {
     Debug.Assert(mapping is HandlerForReferenceType && mapping.IsNullable);
     _mapping      = mapping;
     Type          = t;
     GenCSharpName = t.Type.ToCSharpName();
     _nonNullable  = new HandlerForNonNullableTypeMapping(this);
 }
        PocoJsonPropertyInfo?HandleUnionType(IPocoPropertyInfo p,
                                             IActivityMonitor monitor,
                                             JsonSerializationCodeGen jsonCodeGen,
                                             ITypeScopePart write,
                                             ITypeScopePart read,
                                             ref bool isPocoECMAScriptStandardCompliant,
                                             JsonCodeGenHandler mainHandler)
        {
            // Analyses the UnionTypes and creates the handler for each of them.
            // - Forbids ambiguous mapping for ECMAScriptStandard: all numerics are mapped to "Number" or "BigInt" (and arrays or lists are arrays).
            // - The ECMAScriptStandard projected name must be unique (and is associated to its actual handler).
            // -
            var allHandlers = new List <JsonCodeGenHandler>();
            var checkDuplicatedStandardName = new Dictionary <string, List <JsonCodeGenHandler> >();

            // Gets all the handlers and build groups of ECMAStandardJsnoName handlers (only if the Poco is still standard compliant).
            foreach (var union in p.PropertyUnionTypes)
            {
                var h = jsonCodeGen.GetHandler(union);
                if (h == null)
                {
                    return(null);
                }
                allHandlers.Add(h);
                if (isPocoECMAScriptStandardCompliant && h.HasECMAScriptStandardJsonName)
                {
                    var n = h.ECMAScriptStandardJsonName;
                    if (checkDuplicatedStandardName.TryGetValue(n.Name, out var exists))
                    {
                        exists.Add(h);
                    }
                    else
                    {
                        checkDuplicatedStandardName.Add(n.Name, new List <JsonCodeGenHandler>()
                        {
                            h
                        });
                    }
                }
            }
            allHandlers.Sort((h1, h2) => h1.TypeInfo.TypeSpecOrder.CompareTo(h2.TypeInfo.TypeSpecOrder));

            // Analyze the groups (only if the Poco is still standard compliant).
            List <JsonCodeGenHandler>?ecmaStandardReadhandlers = null;
            bool isECMAScriptStandardCompliant = isPocoECMAScriptStandardCompliant;

            if (isECMAScriptStandardCompliant)
            {
                foreach (var group in checkDuplicatedStandardName.Values)
                {
                    if (group.Count > 1)
                    {
                        int idxCanocical = group.IndexOf(h => h.ECMAScriptStandardJsonName.IsCanonical);
                        if (idxCanocical == -1)
                        {
                            monitor.Warn($"{p} UnionType '{group.Select( h => h.GenCSharpName ).Concatenate( "' ,'" )}' types mapped to the same ECMAScript standard name: '{group[0].ECMAScriptStandardJsonName.Name}' and none of them is the 'Canonical' form. De/serializing this Poco in 'ECMAScriptstandard' will throw a NotSupportedException.");
                            isECMAScriptStandardCompliant = false;
                            break;
                        }
                        var winner = group[idxCanocical];
                        monitor.Trace($"{p} UnionType '{group.Select( h => h.GenCSharpName ).Concatenate( "' ,'" )}' types will be read as {winner.GenCSharpName} in ECMAScript standard name.");
                        if (ecmaStandardReadhandlers == null)
                        {
                            ecmaStandardReadhandlers = allHandlers.Where(h => !h.HasECMAScriptStandardJsonName).ToList();
                        }
                        ecmaStandardReadhandlers.Add(winner);
                    }
                    else
                    {
                        monitor.Debug($"{p} UnionType unambiguous mapping in ECMAScript standard name from '{group[0].ECMAScriptStandardJsonName.Name}' to '{group[0].GenCSharpName}'.");
                        if (ecmaStandardReadhandlers == null)
                        {
                            ecmaStandardReadhandlers = allHandlers.Where(h => !h.HasECMAScriptStandardJsonName).ToList();
                        }
                        ecmaStandardReadhandlers.Add(group[0]);
                    }
                }
                isPocoECMAScriptStandardCompliant &= isECMAScriptStandardCompliant;
            }
            // Invariant: handlers are by design associated to different "oblivious NRT" types: switch case can be done on them.
            // That means that the actual's object type is enough to identify the exact handler (in THE CONTEXT of this property type!).
            // And the property setter controls the assignation: the set of types is controlled.
            //
            // It is tempting to simply call the generic write function but this one uses the GenericWriteHandler that is the "oblivious NRT" type:
            // even if this union exposes a ISet<string>? (nullable of non-nullable), it will be a ISet<string?> (non-nullable - since the GenericWriteHandler
            // is by design a NonNullHandler - of nullable - that is the oblivious nullability for reference type) that will be serialized.
            //
            // Actually the type name doesn't really matter, it's just a convention that a client must follow to receive or send data: here, we could
            // perfectly use the index of the type in the union types, that would be an identifier "local to this property" but this would do the job.
            //
            // What really matters is to identify the function that will read the data with the right null handling so that no nulls can be
            // injected where it should not AND to use the right function to write the data, the one that will not let unexpected nulls emitted.
            // Regarding this, using the GenericWriteHandler is definitely not right.
            //
            // That's why we generate a dedicated switch-case for writing here. If one of the handler is bound to the ObjectType (currently
            // that's true when jsonTypeInfo.IsFinal is false), we call the generic write object in the default: case.
            //
            Debug.Assert(!allHandlers.Select(h => h.TypeInfo.GenCSharpName).GroupBy(Util.FuncIdentity).Any(g => g.Count() > 1));

            var info = new PocoJsonPropertyInfo(p, allHandlers, isECMAScriptStandardCompliant ? ecmaStandardReadhandlers : null);

            _finalReadWrite.Add(() =>
            {
                var fieldName = "_v" + info.PropertyInfo.Index;
                write.Append("w.WritePropertyName( ").AppendSourceString(info.PropertyInfo.PropertyName).Append(" );").NewLine();
                if (info.IsJsonUnionType)
                {
                    write.GeneratedByComment()
                    .Append(@"switch( ").Append(fieldName).Append(" )")
                    .OpenBlock()
                    .Append("case null: ");
                    if (info.PropertyInfo.IsNullable)
                    {
                        write.Append("w.WriteNullValue();").NewLine()
                        .Append("break;");
                    }
                    else
                    {
                        write.Append(@"throw new InvalidOperationException( ""A null value appear where it should not. Writing JSON is impossible."" );");
                    }
                    write.NewLine();
                    bool hasDefaultObject = false;
                    foreach (var h in info.AllHandlers)
                    {
                        Debug.Assert(!h.IsNullable, "Union types are not nullable by design (null has been generalized).");
                        if (h.TypeInfo.IsFinal)
                        {
                            write.Append("case ").Append(h.TypeInfo.MostAbstractMapping?.GenCSharpName ?? h.TypeInfo.GenCSharpName).Append(" v: ").NewLine();
                            h.DoGenerateWrite(write, "v", handleNull: false, writeTypeName: true);
                            write.NewLine().Append("break;").NewLine();
                        }
                        else
                        {
                            hasDefaultObject = true;
                        }
                    }
                    write.Append(@"default:").NewLine();
                    if (hasDefaultObject)
                    {
                        mainHandler.ToNonNullHandler().GenerateWrite(write, fieldName);
                        write.NewLine().Append("break;");
                    }
                    else
                    {
                        write.Append(@"throw new InvalidOperationException( $""Unexpected type {").Append(fieldName).Append(@".GetType()} in union ").Append(info.PropertyInfo.ToString() !).Append(@"."" );");
                    }
                    write.CloseBlock();
                }
                else
                {
                    info.AllHandlers[0].GenerateWrite(write, fieldName);
                }

                read.Append("case ").AppendSourceString(info.PropertyInfo.PropertyName).Append(": ")
                .OpenBlock();

                if (info.IsJsonUnionType)
                {
                    read.Append("if( r.TokenType == System.Text.Json.JsonTokenType.Null )");
                    if (info.PropertyInfo.IsNullable)
                    {
                        read.OpenBlock()
                        .Append(fieldName).Append(" = null;").NewLine()
                        .Append("r.Read();")
                        .CloseBlock()
                        .Append("else")
                        .OpenBlock();
                    }
                    else
                    {
                        read.Append(" throw new System.Text.Json.JsonException(\"").Append(info.PropertyInfo.ToString() !).Append(" cannot be null.\");").NewLine();
                    }

                    if (info.IsJsonUnionType)
                    {
Beispiel #3
0
 public HandlerForNonNullableTypeMapping(HandlerForTypeMapping nullable)
 {
     Debug.Assert(nullable.TypeMapping != null && nullable.IsNullable);
     _nullable = nullable;
     _mapping  = _nullable.TypeMapping.ToNonNullHandler();
 }
Beispiel #4
0
        JsonTypeInfo?TryRegisterInfoForValueTuple(NullableTypeTree t, IReadOnlyList <NullableTypeTree> types)
        {
            JsonCodeGenHandler[] handlers = new JsonCodeGenHandler[types.Count];
            bool isJSCanonical            = true;
            var  jsonName   = new StringBuilder("[");
            var  jsJsonName = new StringBuilder("[");

            for (int i = 0; i < types.Count; i++)
            {
                if (i > 0)
                {
                    jsonName.Append(',');
                    jsJsonName.Append(',');
                }
                var h = GetHandler(types[i]);
                if (h == null)
                {
                    return(null);
                }
                handlers[i] = h;
                jsonName.Append(h.JsonName);
                isJSCanonical &= h.TypeInfo.NonNullableECMAScriptStandardJsonName.IsCanonical;
                jsJsonName.Append(h.TypeInfo.NonNullableECMAScriptStandardJsonName.Name);
            }
            jsonName.Append(']');
            jsJsonName.Append(']');

            JsonTypeInfo?info = AllowTypeInfo(t.ToNormalNull(), jsonName.ToString());

            if (info == null)
            {
                return(null);
            }

            info.SetECMAScriptStandardName(jsJsonName.ToString(), isJSCanonical);

            // Don't use 'in' modifier on non-readonly structs: See https://devblogs.microsoft.com/premier-developer/the-in-modifier-and-the-readonly-structs-in-c/
            // We use a 'ref' instead (ValueTuple TypeInfo below use SetByRefWriter).
            var fWriteDef = FunctionDefinition.Parse("internal static void WriteVT_" + info.NumberName + "( System.Text.Json.Utf8JsonWriter w, ref " + info.GenCSharpName + " v, PocoJsonSerializerOptions options )");
            var fReadDef  = FunctionDefinition.Parse("internal static void ReadVT_" + info.NumberName + "( ref System.Text.Json.Utf8JsonReader r, out " + info.GenCSharpName + " v, PocoJsonSerializerOptions options )");

            IFunctionScope?fWrite = _pocoDirectory.FindFunction(fWriteDef.Key, false);
            IFunctionScope?fRead;

            if (fWrite != null)
            {
                fRead = _pocoDirectory.FindFunction(fReadDef.Key, false);
                Debug.Assert(fRead != null);
            }
            else
            {
                fWrite = _pocoDirectory.CreateFunction(fWriteDef);
                fRead  = _pocoDirectory.CreateFunction(fReadDef);
                _finalReadWrite.Add(m =>
                {
                    fWrite.Append("w.WriteStartArray();").NewLine();
                    int itemNumber = 0;
                    foreach (var h in handlers)
                    {
                        h.GenerateWrite(fWrite, "v.Item" + (++itemNumber).ToString(CultureInfo.InvariantCulture));
                    }
                    fWrite.Append("w.WriteEndArray();").NewLine();

                    fRead.Append("r.Read();").NewLine();

                    itemNumber = 0;
                    foreach (var h in handlers)
                    {
                        h.GenerateRead(fRead, "v.Item" + (++itemNumber).ToString(CultureInfo.InvariantCulture), true);
                    }
                    fRead.Append("r.Read();").NewLine();
                });
            }

            info.SetByRefWriter()
            .Configure(
                (ICodeWriter write, string variableName) =>
            {
                write.Append("PocoDirectory_CK.").Append(fWrite.Definition.MethodName.Name).Append("( w, ref ").Append(variableName).Append(", options );");
            },
                (ICodeWriter read, string variableName, bool assignOnly, bool variableCanBeNull) =>
            {
                string vName = variableName;
                if (variableCanBeNull)
                {
                    read.OpenBlock()
                    .Append(info.GenCSharpName).Append(" notNull;").NewLine();
                    vName = "notNull";
                }
                read.Append("PocoDirectory_CK.").Append(fRead.Definition.MethodName.Name).Append("( ref r, out ").Append(vName).Append(", options );");
                if (variableCanBeNull)
                {
                    read.Append(variableName).Append(" = notNull;")
                    .CloseBlock();
                }
            });

            return(info);
        }