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) {
public HandlerForNonNullableTypeMapping(HandlerForTypeMapping nullable) { Debug.Assert(nullable.TypeMapping != null && nullable.IsNullable); _nullable = nullable; _mapping = _nullable.TypeMapping.ToNonNullHandler(); }
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); }