使用Newtonsoft创建无效的Json-允许无效的对象吗? [英] Create invalid Json with Newtonsoft - Allow invalid objects?

查看:50
本文介绍了使用Newtonsoft创建无效的Json-允许无效的对象吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在故意尝试用Newtonsoft Json创建无效的JSON,以便放置一个ESI包含标签,该标签将获取另外两个JSON节点.

I'm deliberately trying to create invalid JSON with Newtonsoft Json, in order to place an ESI include tag, which will fetch two more json nodes.

这是我的JsonConverter的WriteJson方法:

This is my JsonConverter's WriteJson method:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    mApiResponseClass objectFromApi = (mApiResponseClass)value;

    foreach (var obj in objectFromApi.GetType().GetProperties())
    {
        if (obj.Name == "EsiObj")
        {
            writer.WriteRawValue(objectFromApi.EsiObj);
        }
        else
        {
            writer.WritePropertyName(obj.Name);
            serializer.Serialize(writer, obj.GetValue(value, null));
        }
    }
}

mApiResponseClass中的EsiObj只是一个字符串,但是需要将其写入JSON响应中以进行解释,而无需任何属性名称-这样ESI才能正常工作.

The EsiObj in mApiResponseClass is just a string, but it needs to be written into the JSON response to be interpretted without any property name - so that hte ESI can work.

这当然会导致Json Writer发生异常,值:

This of course results in an exception with the Json Writer, with value:

Newtonsoft.Json.JsonWriterException:'状态对象中的令牌未定义将导致无效的JSON对象.路径."

Newtonsoft.Json.JsonWriterException: 'Token Undefined in state Object would result in an invalid JSON object. Path ''.'

有什么办法解决这个问题吗?

Is there any way around this?

一个理想的输出将是JSON格式,从技术上讲是无效的,并且看起来像这样:

An ideal output from this would be JSON formatted, technically not valid, and would look like this:

{
value:7,
string1:"woohoo",
<esi:include src="/something" />
Song:["I am a small API","all i do is run","but from who?","nobody knows"]
}

修改:使用ESI,我们可以使单个响应具有不同的缓存长度-即,我们可以将可以长时间缓存的数据放置在JSON的某些部分中,并且仅获取更新的部分,例如那些依赖于特定于客户端的部分数据.ESI不是特定于HTML的.(如下所示)通过支持这些标签的Varnish运行.不幸的是,要求我们仅发出1个文件作为响应,并且不需要客户的进一步请求.我们也不能更改响应-所以我不能只添加一个专门包含其他节点的JSON节点.

Using ESI allows us to have varying cache lengths of a single response - i.e. we can place data that can be cached for a very long time in some parts of the JSON, and only fetch updated parts, such as those that rely on client-specific data. ESI is not HTML specific. (As some state below) It's being run via Varnish, which supports these tags. Unfortunately, it's required that we do only put out 1 file as a response, and require no further request from the Client. We cannot alter our response either - so i can't just add a JSON node specifically to contain the other nodes.

通过ESI向我们的后端进一步请求用户/客户端特定数据(即另一个端点)来解决更多json节点"部分.预期的结果是,我们随后将原始JSON文档与后来的JSON文档无缝地合并在一起.(这样,原始文档可能会很旧,而特定于客户的文档可能会很新)

Edit 2: The "more json nodes" part is solved by ESI making a further request to our backend for user/client specific data, i.e. to another endpoint. The expected result is that we then merge the original JSON document and the later requested one together seamlessly. (This way, the original document can be old, and client-specific can be new)

修改3:端点/something将输出类似JSON的片段,例如:

Edit 3: The endpoint /something would output JSON-like fragments like:

teapots:[ {Id: 1, WaterLevel: 100, Temperature: 74, ShortAndStout: true}, {Id: 2, WaterLevel: 47, Temperature: 32, ShortAndStout: true} ],

总答复为:

{
value:7,
string1:"woohoo",
teapots:[ {Id: 1, WaterLevel: 100, Temperature: 74, ShortAndStout: true}, {Id: 2, WaterLevel: 47, Temperature: 32, ShortAndStout: true} ],
Song:["I am a small API","all i do is run","but from who?","nobody knows"]
}

推荐答案

您的基本问题是 JSON状态,并验证从状态到状态的转换,从而确保不会编写结构错误的JSON.这是通过两种不同的方式使您绊倒.

Your basic problem is that a JsonWriter is a state machine, tracking the current JSON state and validating transitions from state to state, thereby ensuring that badly structured JSON is not written. This is is tripping you up in two separate ways.

首先,您的 WriteJson()方法未调用 WriteEndObject() .这些是围绕JSON对象编写 {} 的方法.由于您的理想输出"显示这些括号,您应该在 WriteJson()的开始和结尾处添加对这些方法的调用.

Firstly, your WriteJson() method is not calling WriteStartObject() and WriteEndObject(). These are the methods that write the { and } around a JSON object. Since your "ideal output" shows these braces, you should add calls to these methods at the beginning and end of your WriteJson().

其次,您正在调用 WriteRawValue(),这是因为格式正确的JSON不允许出现值,特别是在期望使用属性名称的地方.由于文档指出,这有望引起异常.:

Secondly, you are calling WriteRawValue() at a point where well-formed JSON would not allow a value to occur, specifically where a property name is expected instead. It is expected that this would cause an exception, since the documentation states:

在期望值的地方写入原始JSON并更新编写者的状态.

Writes raw JSON where a value is expected and updates the writer's state.

您可以改用的是 WriteRaw(),它是

What you can instead use is WriteRaw() which is documented as follows:

在不更改编写者状态的情况下写入原始JSON.

Writes raw JSON without changing the writer's state.

但是, WriteRaw()不会帮您任何忙.具体来说,您需要自己编写任何定界符和缩进.

However, WriteRaw() won't do you any favors. In specific, you will need to take care of writing any delimiters and indentation yourself.

修复是将转换器修改为类似于以下内容的

The fix would be to modify your converter to look something like:

public class EsiObjConverter<T> : JsonConverter
{
    const string EsiObjName = "EsiObj";

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(value.GetType()) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException(string.Format("Non-object type {0}", value));
        writer.WriteStartObject();
        int propertyCount = 0;
        bool lastWasEsiProperty = false;
        foreach (var property in contract.Properties.Where(p => p.Readable && !p.Ignored))
        {
            if (property.UnderlyingName == EsiObjName && property.PropertyType == typeof(string))
            {
                var esiValue = (string)property.ValueProvider.GetValue(value);
                if (!string.IsNullOrEmpty(esiValue))
                {
                    if (propertyCount > 0)
                    {
                        WriteValueDelimiter(writer);
                    }
                    writer.WriteWhitespace("\n");
                    writer.WriteRaw(esiValue);
                    // If it makes replacement easier, you could force the ESI string to be on its own line by calling
                    // writer.WriteWhitespace("\n");

                    propertyCount++;
                    lastWasEsiProperty = true;
                }
            }
            else
            {
                var propertyValue = property.ValueProvider.GetValue(value);

                // Here you might check NullValueHandling, ShouldSerialize(), ...

                if (propertyCount == 1 && lastWasEsiProperty)
                {
                    WriteValueDelimiter(writer);
                }
                writer.WritePropertyName(property.PropertyName);
                serializer.Serialize(writer, propertyValue);

                propertyCount++;
                lastWasEsiProperty = false;
            }
        }
        writer.WriteEndObject();
    }

    static void WriteValueDelimiter(JsonWriter writer)
    {
        var args = new object[0];
        // protected virtual void WriteValueDelimiter() 
        // https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonWriter_WriteValueDelimiter.htm
        // Since this is overridable by client code it is unlikely to be removed.
        writer.GetType().GetMethod("WriteValueDelimiter", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(writer, args);
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override bool CanRead { get { return false; } }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

序列化的输出将是:

{
  "value": 7,
  "string1": "woohoo",
<esi:include src="/something" />,
  "Song": [
    "I am a small API",
    "all i do is run",
    "but from who?",
    "nobody knows"
  ]
}

现在,在您的问题中,所需的JSON输出显示未正确引用的JSON属性名称.如果您确实需要此功能,而不仅仅是问题的错字,则可以通过设置此答案所示 Json.Net-通过"https://stackoverflow.com/users/893099/christophe-geers>克里斯托弗·盖尔斯:

Now, in your question, your desired JSON output shows JSON property names that are not properly quoted. If you really need this and it is not just a typo in the question, you can accomplish this by setting JsonTextWriter.QuoteName to false as shown in this answer to Json.Net - Serialize property name without quotes by Christophe Geers:

var settings = new JsonSerializerSettings
{
    Converters = { new EsiObjConverter<mApiResponseClass>() },
};    
var stringWriter = new StringWriter();
using (var writer = new JsonTextWriter(stringWriter))
{
    writer.QuoteName = false;
    writer.Formatting = Formatting.Indented;
    writer.Indentation = 0;
    JsonSerializer.CreateDefault(settings).Serialize(writer, obj);
}

这将导致:

{
value: 7,
string1: "woohoo",
<esi:include src="/something" />,
Song: [
"I am a small API",
"all i do is run",
"but from who?",
"nobody knows"
]
}

这几乎是您的问题中显示的内容,但不完全是.它在ESI字符串和下一个属性之间包含一个逗号分隔符,但是在您的问题中没有分隔符:

This is almost what is shown in your question, but not quite. It includes a comma delimiter between the ESI string and the next property, but in your question there is no delimiter:

<esi:include src="/something" /> Song: [ ... ]

摆脱定界符实际上难以实现,因为 <代码> JsonTextWriter.WritePropertyName() 在不在对象开头时会自动写入分隔符.但是,我认为这应该是可以接受的.ESI本身不知道是要替换对象的first,last还是Middle属性,因此似乎最好不要在替换字符串中包含定界符.

Getting rid of the delimiter turns out to be problematic to implement because JsonTextWriter.WritePropertyName() automatically writes a delimiter when not at the beginning of an object. I think, however, that this should be acceptable. ESI itself will not know whether it is replacing the first, last or middle property of an object, so it seems best to not include the delimiter in the replacement string at all.

工作示例.Net小提琴此处.

Working sample .Net fiddle here.

这篇关于使用Newtonsoft创建无效的Json-允许无效的对象吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆