Создать недействительный Json с Newtonsoft - Разрешить недействительные объекты? - PullRequest
0 голосов
/ 13 ноября 2018

Я намеренно пытаюсь создать недопустимый JSON с помощью Newtonsoft Json, чтобы разместить тег включения ESI, который будет извлекать еще два узла json.

Это метод WriteJson моего JsonConverter:

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

EsiObj в mApiResponseClass - это просто строка, но ее нужно записать в ответ JSON, чтобы ее можно было интерпретировать без имени свойства, чтобы ESI мог работать.

Это, конечно, приводит к исключению в Json Writer со значением:

Newtonsoft.Json.JsonWriterException: 'токен не определен в состоянии объекта приведет к неверному объекту JSON. Путь ''. '

Есть ли способ обойти это?

Идеальный выходной результат - формат JSON, технически недопустимый и будет выглядеть так:

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

Edit: Использование ESI позволяет нам иметь разную длину кэша одного ответа - т.е. мы можем помещать данные, которые могут быть кэшированы в течение очень долгого времени, в некоторые части JSON и извлекать только обновленные части, такие как те, которые зависят от конкретного клиента. данные. ESI не является специфичным для HTML. (Как указано ниже) Это выполняется через Varnish, который поддерживает эти теги. К сожалению, требуется, чтобы мы выдавали только 1 файл в качестве ответа и не требовали дальнейшего запроса от Клиента. Мы также не можем изменить наш ответ - поэтому я не могу просто добавить узел JSON, специально содержащий другие узлы.

Редактировать 2: Часть "больше узлов json" решается с помощью ESI, делающего дополнительный запрос к нашему бэкэнду для данных, специфичных для пользователя / клиента, то есть к другой конечной точке. Ожидаемый результат состоит в том, что мы затем объединяем исходный документ JSON и последующий запрошенный вместе без проблем. (Таким образом, исходный документ может быть старым, а конкретный клиент может быть новым)

Редактировать 3: Конечная точка / что-то выдаст JSON-подобные фрагменты, такие как:

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"]
}

1 Ответ

0 голосов
/ 13 ноября 2018

Ваша основная проблема заключается в том, что JsonWriter является конечным автоматом, отслеживающим текущее состояние JSON и проверяющим переходы из состояния в состояние, таким образом гарантируя, что плохо структурированный JSON не будетнаписано.Это отключает вас двумя разными способами.

Во-первых , ваш WriteJson() метод не вызывает WriteStartObject() и WriteEndObject().Это методы, которые записывают { и } вокруг объекта JSON.Поскольку ваш «идеальный вывод» показывает эти скобки, вы должны добавить вызовы к этим методам в начале и в конце вашего WriteJson().

Во-вторых , вы звоните WriteRawValue() вточка, в которой правильно сформированный JSON не позволил бы появиться значению, особенно там, где вместо него ожидается имя свойства.Ожидается, что это вызовет исключение, поскольку документация гласит:

Записывает необработанный JSON, где ожидается значение, и обновляет состояние устройства записи.

Вместо этого вы можете использовать WriteRaw(), что задокументировано следующим образом:

Записывает необработанный JSON без изменения состояния записывающего устройства.

Однако, WriteRaw() не окажет вам никакой пользы.В частности, вам нужно будет позаботиться о написании любых разделителей и отступов самостоятельно.

Исправление должно было бы изменить ваш конвертер, чтобы он выглядел примерно так:

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, которые не указаны в кавычках.Если вам это действительно нужно, и это не просто опечатка в вопросе, вы можете сделать это, установив JsonTextWriter.QuoteName в false, как показано в в этом ответе в Json.Net - Сериализация имени свойства без кавычек от Кристоф Гирс :

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 и следующим свойством, но в вашем вопросе нет разделителя:

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

Избавиться от разделителя оказывается проблематичным для реализации, потому что JsonTextWriter.WritePropertyName() автоматически записывает разделитель, когда не в начале объекта.Я думаю, однако, что это должно быть приемлемым.Сам ESI не будет знать, заменяет ли оно первое, последнее или среднее свойство объекта, поэтому лучше не включать разделитель в строку замены.

Рабочий пример .Net fiddle здесь .

...