예제 #1
0
        private void WriteTypeDiscriminatorConverterBody(SourceWriter w, string baseType, JensonPropertyInfo p, string discrFun)
        {
            w.Line($"public override {baseType} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)");
            w.Line("{");

            w.Indent();

            w.Line("var discrReader = reader;");
            w.Line($"var discriminatorName = Encoding.UTF8.GetBytes(\"{p.Name}\");");
            w.Line();

            w.Line("if (discrReader.TokenType != JsonTokenType.StartObject) throw new JsonException(\"Expected start of object.\");");
            w.Line();

            w.Line("while (discrReader.Read())");
            w.Line("{");
            w.Indent();
            w.Line("if (discrReader.ValueTextEquals(discriminatorName))");
            w.Line("{");
            w.Indent();
            w.Line("if (!discrReader.Read()) throw new JsonException();");
            // TODO proper deserialization of value
            w.Line($"var value = JsonSerializer.Deserialize<{p.TypeName}>(ref discrReader);");
            w.Line($"var realType = {baseType}.{discrFun}(value);");
            w.Line($"return ({baseType}) JsonSerializer.Deserialize(ref reader, realType, options);");
            w.Dedent();
            w.Line("}");

            w.Line();
            w.Line("discrReader.Skip();");

            w.Dedent();
            w.Line("}");

            w.Line();

            w.Line("throw new JsonException(\"Missing type discriminator.\");");

            w.Dedent();
            w.Line("}");

            w.Line();

            w.Line($"public override void Write(Utf8JsonWriter writer, {baseType} value, JsonSerializerOptions options)");
            w.Line("{");
            w.Indent();

            w.Line("throw new NotImplementedException(\"A converter for subclasses must always be used for writing.\");");

            w.Dedent();
            w.Line("}");
        }
예제 #2
0
        private string GenerateReadArray(JensonPropertyInfo p)
        {
            var w = new SourceWriter();

            w.Line("if (reader.TokenType != JsonTokenType.StartArray) throw new JsonException();");
            w.Line();
            w.Line($"{p.Name} = JsonSerializer.Deserialize<{p.TypeName}>(ref reader);");

            return(w.ToString());

            w.Line("var restore = reader;");
            w.Line("var length = 0;");
            w.Line();
            w.Line("while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)");
            w.Line("{");

            w.Indent();

            w.Line("if (reader.TokenType != JsonTokenType.Comment) length++;");
            w.Line("reader.Skip();");
            w.Dedent();
            w.Line("}");

            w.Line();

            w.Line("reader = restore;");

            w.Line($"{p.Name} = new {p.TypeName.Substring(0, p.TypeName.Length - 2)}[length];");

            var itemType = p.ArrayItemType;

            if (itemType.EndsWith("[]"))
            {
                throw new NotImplementedException("Nested arrays are not implemented.");
            }

            //var attribList = new List<OpAttribute>();
            //while (reader.Read())
            //{
            //    if (reader.TokenType == JsonTokenType.EndArray)
            //    {
            //        attributes = attribList.ToArray();
            //        break;
            //    }

            return(w.ToString());
        }
예제 #3
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("}");
        }
예제 #4
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("}");
        }
예제 #5
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);
        }