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("}"); }
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()); }
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("}"); }
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); }