private void AppendWrite(SourceWriter w, string typeName, List <JensonPropertyInfo> propertiesInfo) { // WRITE // Relevant options // - PropertyNameCaseInsensitive // - IgnoreNullValues // - IgnoreReadOnlyProperties w.Line($"public override void Write(Utf8JsonWriter writer, {typeName} value, JsonSerializerOptions options)"); w.Line("{"); w.Indent(); w.Line("writer.WriteStartObject();"); w.Line(); for (var i = 0; i < propertiesInfo.Count; i++) { var p = propertiesInfo[i]; if (!p.ShouldSerialize) { continue; } var writeValueString = p switch { //_ when p.IsArray => GenerateWriteArray(p), _ when p.IsString || p.IsBoolean || p.IsDateTime || p.IsDateTimeOffset || p.IsGuid => $"writer.WriteString(_{p.Name}Name, value.{p.Name});", _ when p.IsByte || p.IsDecimal || p.IsDouble || p.IsInt16 || p.IsInt32 || p.IsInt64 || p.IsSByte || p.IsSingle || p.IsUInt16 || p.IsUInt32 || p.IsUInt64 => $"writer.WriteNumber(_{p.Name}Name, value.{p.Name});", _ => $"JsonSerializer.Serialize(writer, value.{p.Name}, options);" }; if (p.IsReadOnly) { w.Line($"if (!options.IgnoreReadOnlyProperties)"); w.Line("{"); w.Indent(); } if (p.ShouldSerializeFunction != null) { w.Line($"if (value.{p.ShouldSerializeFunction}())"); w.Line("{"); w.Indent(); } if (p.CanBeNull) { w.Line($"if (!(options.IgnoreNullValues && value.{p.Name} is null))"); w.Line("{"); w.Indent(); } w.Line(writeValueString); if (p.CanBeNull) { w.Dedent(); w.Line("}"); } if (p.ShouldSerializeFunction != null) { w.Dedent(); w.Line("}"); } if (p.IsReadOnly) { w.Dedent(); w.Line("}"); } } w.Line(); w.Line("writer.WriteEndObject();"); w.Dedent(); w.Line("}"); }
private void AppendRead(SourceWriter w, string typeName, List <JensonPropertyInfo> propertiesInfo) { // READ // Relevant options: // - IgnoreNullValues // - ReadCommentHandling // - AllowTrailingComments w.Line($"public override {typeName} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)"); w.Line("{"); w.Indent(); w.Line("if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException(\"Expected start of object.\");"); w.Line(); foreach (var p in propertiesInfo.Where(p => p.ShouldDeserialize)) { // variables to hold content w.Line($"{p.TypeName} {p.Name} = default;"); } w.Line(); w.Line("while (reader.Read())"); w.Line("{"); w.Indent(); w.Line("if (reader.TokenType == JsonTokenType.EndObject)"); w.Line("{"); w.Indent(); // Construct and return the read object w.Line($"return new {typeName}"); w.Line("{"); w.Indent(); foreach (var p in propertiesInfo.Where(p => p.ShouldDeserialize)) { w.Line($"{p.Name} = {p.Name},"); } w.Dedent(); w.Line("};"); w.Dedent(); w.Line("}"); w.Line(); w.Line("if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException($\"Expected property name, but got {reader.TokenType}.\");"); w.Line(); var firstBranchHandled = false; for (var i = 0; i < propertiesInfo.Count; i++) { var p = propertiesInfo[i]; if (!p.ShouldDeserialize) { continue; } // Check against properties if (firstBranchHandled) { w.Write($"else "); } firstBranchHandled = true; // TODO options.PropertyNameCaseInsensitive // TODO options.PropertyNamingPolicy w.Line($"if (reader.ValueTextEquals(_{p.Name}Name))"); w.Line("{"); w.Indent(); var tryReadValue = p switch { //_ when p.IsArray => GenerateReadArray(p), _ when p.IsString => $"if (!reader.Read()) throw new JsonException();\n{p.Name} = reader.GetString();", _ when p.IsBoolean => GenerateReadBoolean(p), _ when p.IsByte => $"if (!reader.Read() || !reader.TryGetByte(out {p.Name})) throw new JsonException();", _ when p.IsDateTime => $"if (!reader.Read() || !reader.TryGetDateTime(out {p.Name})) throw new JsonException();", _ when p.IsDateTimeOffset => $"if (!reader.Read() || !reader.TryGetDateTimeOffset(out {p.Name})) throw new JsonException();", _ when p.IsDecimal => $"if (!reader.Read() || !reader.TryGetDecimal(out {p.Name})) throw new JsonException();", _ when p.IsDouble => $"if (!reader.Read() || !reader.TryGetDouble(out {p.Name})) throw new JsonException();", _ when p.IsGuid => $"if (!reader.Read() || !reader.TryGetGuid(out {p.Name})) throw new JsonException();", _ when p.IsInt16 => $"if (!reader.Read() || !reader.TryGetInt16(out {p.Name})) throw new JsonException();", _ when p.IsInt32 => $"if (!reader.Read() || !reader.TryGetInt32(out {p.Name})) throw new JsonException();", _ when p.IsInt64 => $"if (!reader.Read() || !reader.TryGetInt64(out {p.Name})) throw new JsonException();", _ when p.IsSByte => $"if (!reader.Read() || !reader.TryGetSByte(out {p.Name})) throw new JsonException();", _ when p.IsSingle => $"if (!reader.Read() || !reader.TryGetSingle(out {p.Name})) throw new JsonException();", _ when p.IsUInt16 => $"if (!reader.Read() || !reader.TryGetUInt16(out {p.Name})) throw new JsonException();", _ when p.IsUInt32 => $"if (!reader.Read() || !reader.TryGetUInt32(out {p.Name})) throw new JsonException();", _ when p.IsUInt64 => $"if (!reader.Read() || !reader.TryGetUInt64(out {p.Name})) throw new JsonException();", _ => $"{p.Name} = JsonSerializer.Deserialize<{p.TypeName}>(ref reader);" }; w.Line(tryReadValue); w.Dedent(); w.Line("}"); } // handle no matching properties if (firstBranchHandled) { w.Write("else"); w.Write("{"); w.Indent(); } // TODO handle complex values (using Skip) w.Line("if (!reader.Read()) throw new JsonException();"); if (firstBranchHandled) { w.Dedent(); w.Write("}"); } w.Dedent(); w.Line("}"); w.Line(); w.Line("throw new JsonException(\"Missing close object token. Invalid JSON.\");"); w.Dedent(); w.Line("}"); }
public void Execute(SourceGeneratorContext generatorContext) { var ct = generatorContext.CancellationToken; var compilation = generatorContext.Compilation; var jensonSerializeAttribute = compilation.GetTypeByMetadataName("Jenson.Attributes.JensonSerializeAttribute"); var jensonPropertyAttribute = compilation.GetTypeByMetadataName("Jenson.Attributes.JensonPropertyAttribute"); var jensonTypeDiscriminatorAttribute = compilation.GetTypeByMetadataName("Jenson.Attributes.JensonTypeDiscriminatorAttribute"); var jsonPropertyNameAttribute = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonPropertyNameAttribute"); var jsonIgnoreAttribute = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonIgnoreAttribute"); if (jensonSerializeAttribute is null) { throw new Exception("Missing JensonSerializeAttribute in compilation."); } if (jensonPropertyAttribute is null) { throw new Exception("Missing JensonPropertyAttribute in compilation."); } if (jensonTypeDiscriminatorAttribute is null) { throw new Exception("Missing JensonTypeDiscriminatorAttribute in compilation."); } if (jsonPropertyNameAttribute is null) { throw new Exception("Missing JsonPropertyName in compilation."); } if (jsonIgnoreAttribute is null) { throw new Exception("Missing JsonIgnore in compilation."); } var context = new JensonContext( generatorContext, jensonSerializeAttribute, jensonPropertyAttribute, jensonTypeDiscriminatorAttribute, jsonPropertyNameAttribute, jsonIgnoreAttribute); var rootTypes = compilation.GlobalNamespace.GetAllTypes(ct) .Where(t => t.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jensonSerializeAttribute))); ct.ThrowIfCancellationRequested(); var sb = new StringBuilder(); foreach (var t in rootTypes) { var typeDeclarations = t.DeclaringSyntaxReferences.Select(sr => { return((TypeDeclarationSyntax)sr.GetSyntax()); }); var isPartial = typeDeclarations.Any(td => td.Modifiers.Any(SyntaxKind.PartialKeyword)); if (!isPartial) { foreach (var td in typeDeclarations) { var diagnostic = Diagnostic.Create(MissingPartialModifier, td.GetLocation(), new object[] { t.Name }); generatorContext.ReportDiagnostic(diagnostic); } } var w = new SourceWriter(); GenerateTypeSerializer(context, w, t); generatorContext.AddSource($"{t.Name}.jenson", SourceText.From(w.ToString(), Encoding.UTF8)); // for debugging sb.Append(w.ToString()); sb.AppendLine(); sb.AppendLine(); } // for debugging File.WriteAllText("C:\\Users\\jesse\\Desktop\\converter.cs", sb.ToString()); }
private void GenerateTypeSerializer(JensonContext context, SourceWriter w, INamedTypeSymbol t) { AppendImports(w); w.Line(); var ns = t.ContainingNamespace; var nsString = ns.Name; ns = ns.ContainingNamespace; while (!ns.IsGlobalNamespace) { nsString = ns.Name + "." + nsString; ns = ns.ContainingNamespace; } w.Line($"namespace {nsString}"); w.Line("{"); w.Indent(); var typeName = t.Name; var declKeyword = t.TypeKind == TypeKind.Class ? (t.IsRecord() ? "record" : "class") : "struct"; w.Line($"[JsonConverter(typeof({typeName}Converter))]"); w.Line($"public partial {declKeyword} {typeName} {{ }}"); var propertiesInfo = ExtractPropertyInfo(context, t, typeName); w.Line($"public class {typeName}Converter : JsonConverter<{typeName}>"); w.Line("{"); w.Indent(); var typeDiscriminatorAttr = t.GetAttributes() .FirstOrDefault(attr => SymbolEqualityComparer.Default.Equals( attr.AttributeClass, context.JensonTypeDiscriminatorAttribute)); if (typeDiscriminatorAttr is not null) { var discrPropName = (string)typeDiscriminatorAttr.ConstructorArguments[0].Value; var discrFun = (string)typeDiscriminatorAttr.ConstructorArguments[1].Value; var discrProp = propertiesInfo.First(p => p.Name.Equals(discrPropName)); WriteTypeDiscriminatorConverterBody(w, typeName, discrProp, discrFun); } else { foreach (var p in propertiesInfo) { // TODO When property naming policy is set this won't necessarily match // UTF-8 json variable names. We should take JsonSerializerOptions into account. // Relevant props: // - PropertyNameCaseInsensitive // - PropertyNamingPolicy // TODO use utf8 string literals when available w.Line($"private static readonly byte[] _{p.Name}Name = Encoding.UTF8.GetBytes(\"{p.JsonName ?? p.Name}\");"); } w.Line(); AppendRead(w, typeName, propertiesInfo); w.Line(); AppendWrite(w, typeName, propertiesInfo); } w.Dedent(); w.Line("}"); // class w.Dedent(); w.Line("}"); // namespace Debug.Assert(w.Indentation == 0); }