Example #1
0
        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("}");
        }
Example #2
0
        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("}");
        }
Example #3
0
        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());
        }
Example #4
0
        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);
        }