Newtonsoft.JSON反序列化字符串化JSON schema报错原因及解决问询
我从客户端接收ChatGPT API请求中代表函数的数据,原本打算先通过Newtonsoft.Json解析收到的JSON,再在解析过程中完成JSchema解析,但实际情况是JSchema解析先于常规JSON解析执行,而且[JsonIgnore]属性被JSchema解析忽略了。
核心问题
- 为何处理循环中的
json1会引发错误? - 为何JSchema解析器期望对象或布尔值,而
ParametersJson的类型是字符串? - 是否存在可行方式,像
json1那样编码JSON并正常反序列化,同时在客户端保持JSON schema为字符串格式?
报错信息
未处理的异常。Newtonsoft.Json.Schema.JSchemaReaderException: 读取schema时遇到意外令牌。预期StartObject、Boolean,实际得到String。路径'parameters',第7行,位置9。
在Newtonsoft.Json.Schema.Infrastructure.JSchemaReader.EnsureToken(JsonReader reader, String name, List`1 tokenTypes, String errorMessage)处
测试代码
using System; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Schema; public class Program { public static void Main() { string json0 = @" { 'Name': 'TestFunction', 'Description': 'This is a test function.', 'parameters': { 'type': 'object', 'properties': { 'prop1': { 'type': 'string' } } } }".Replace("'", "\""); string json1 = @" { 'Name': 'TestFunction', 'Description': 'This is a test function.', 'parameters': '{ 'type': 'object', 'properties': { 'prop1': { 'type': 'string' } } }' }".Replace("'", "\""); var arr = new[]{json0, json1}; foreach (var json in arr) { Function deserializedFunction = JsonConvert.DeserializeObject<Function>(json); Console.WriteLine($"Name: {deserializedFunction.Name}"); Console.WriteLine($"Description: {deserializedFunction.Description}"); Console.WriteLine($"Parameters: {deserializedFunction.Parameters}"); Console.WriteLine("-------------"); } } } #nullable enable public class Function { /// <summary> /// 要调用的函数名称,只能包含a-z、A-Z、0-9、下划线和短横线,最大长度64 /// </summary> public string Name { get; set; } /// <summary> /// 函数功能描述,用于模型判断何时调用该函数 /// </summary> public string? Description { get; set; } /// <summary> /// 函数接受的参数,以JSON Schema对象形式描述 /// <br></br> /// 如果函数不需要参数,传入{"type": "object", "properties": { } } /// </summary> // 需要将客户端传来的字符串转换为JSchema对象 [JsonIgnore] public JSchema Parameters { get; set; } [JsonProperty("parameters")] public string ParametersJson { get => Parameters.ToString(); set => Parameters = JSchema.Parse(value); } public Function(string name, string? description, JSchema parameters) { Name = name; Description = description; Parameters = parameters; } }
1. 处理json1报错的原因
json1的核心问题是JSON格式无效:
你通过Replace("'", "\"")替换单引号后,parameters字段的字符串值内部的双引号没有转义,导致JSON解析器无法正确识别完整的字符串内容。最终JSchema.Parse拿到的是一段损坏、不完整的文本,自然触发解析错误。
2. JSchema解析器期望对象或布尔值的原因
JSchema.Parse要求输入是合法的JSON Schema内容,而JSON Schema的合法格式只能是JSON对象(比如{"type":"object"})或布尔值(true表示允许任何值)。
在处理json1时,因为原始JSON格式错误,ParametersJson的setter拿到的是截断后的无效字符串,解析器无法将其识别为合法的JSON Schema结构,因此抛出"预期对象或布尔值"的错误。
3. 可行的解决方式
要实现客户端传JSON schema字符串、后端正常反序列化为JSchema,需要两步修正:
步骤1:确保客户端传递的JSON格式合法
客户端传递的parameters字符串必须正确转义内部双引号,比如:
// 修正后的json1,用双双引号实现转义,替换后会生成正确的\"转义符 string json1 = @" { 'Name': 'TestFunction', 'Description': 'This is a test function.', 'parameters': '{ ""type"": ""object"", ""properties"": { ""prop1"": { ""type"": ""string"" } } }' ".Replace("'", "\"");
步骤2:修正后端代码
添加无参构造函数(Newtonsoft.Json反序列化必需),并优化ParametersJson的set逻辑,或者使用自定义转换器更灵活地处理字符串与JSchema的转换:
方案A:优化原属性逻辑
#nullable enable public class Function { public string Name { get; set; } public string? Description { get; set; } [JsonIgnore] public JSchema Parameters { get; set; } [JsonProperty("parameters")] public string ParametersJson { get => Parameters.ToString(); set { if (!string.IsNullOrWhiteSpace(value)) { Parameters = JSchema.Parse(value); } } } // 添加无参构造函数,供Newtonsoft.Json反序列化使用 public Function() { Name = string.Empty; Parameters = JSchema.Parse("{}"); } public Function(string name, string? description, JSchema parameters) { Name = name; Description = description; Parameters = parameters; } }
方案B:自定义JsonConverter(更灵活,支持对象/字符串两种输入)
// 自定义转换器,同时支持JSON对象和字符串格式的JSchema public class JSchemaStringConverter : JsonConverter<JSchema> { public override JSchema ReadJson(JsonReader reader, Type objectType, JSchema existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.String) { return JSchema.Parse((string)reader.Value); } else if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.Boolean) { return JSchema.Load(reader); } throw new JsonException("无法识别的JSchema格式"); } public override void WriteJson(JsonWriter writer, JSchema value, JsonSerializer serializer) { writer.WriteValue(value.ToString()); } } // 简化后的Function类 public class Function { public string Name { get; set; } public string? Description { get; set; } [JsonProperty("parameters")] [JsonConverter(typeof(JSchemaStringConverter))] public JSchema Parameters { get; set; } public Function() { Name = string.Empty; Parameters = JSchema.Parse("{}"); } public Function(string name, string? description, JSchema parameters) { Name = name; Description = description; Parameters = parameters; } }
内容的提问来源于stack exchange,提问作者cyclingLinguist

