Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Значительные исправления ПрочитатьJSON (fix #1373 и прочее) #1374

Merged
merged 9 commits into from
Apr 20, 2024
Merged
221 changes: 119 additions & 102 deletions src/ScriptEngine.HostedScript/Library/Json/GlobalJsonFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This Source Code Form is subject to the terms of the
using ScriptEngine.Machine.Contexts;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

namespace ScriptEngine.HostedScript.Library.Json
{
Expand Down Expand Up @@ -55,132 +56,140 @@ public static IAttachableContext CreateInstance()
[ContextMethod("ПрочитатьJSON", "ReadJSON")]
public IValue ReadJSON(JSONReader Reader, bool ReadToMap = false, IValue PropertiesWithDateValuesNames = null, IValue ExpectedDateFormat = null, string ReviverFunctionName = null, IValue ReviverFunctionModule = null, IValue ReviverFunctionAdditionalParameters = null, IValue RetriverPropertiesNames = null, int MaximumNesting = 500)
{
if (ReadToMap)
return ReadJSONInMap(Reader);
else
return ReadJSONInStruct(Reader);
var jsonReader = new JsonReaderInternal(Reader);
return ReadToMap ? jsonReader.Read<MapImpl>() : jsonReader.Read<StructureImpl>();
}

public IValue ReadJSONInStruct(JSONReader Reader, bool nestedArray = false)
internal class JsonReaderInternal
{
if (nestedArray)
{
ArrayImpl NestedArray = new ArrayImpl();
private readonly JSONReader _reader;
private Func<IValue> _builder;
private Action<IValue, string, IValue> _inserter;

while (Reader.Read())
private void Init<TStructure>()
{
if (typeof(TStructure) == typeof(StructureImpl))
{
if (Reader.CurrentJsonTokenType == JsonToken.EndArray)
{
break;
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartObject)
{
NestedArray.Add(ReadJSONInStruct(Reader));
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartArray)
{
NestedArray.Add(ReadJSONInStruct(Reader, true));
}
else
NestedArray.Add(Reader.CurrentValue);
_builder = () => new StructureImpl();
_inserter = (o, s, v) => ((StructureImpl)o).Insert(s, v);
}
else if (typeof(TStructure) == typeof(MapImpl))
{
_builder = () => new MapImpl();
_inserter = (o, s, v) =>((MapImpl)o).Insert(ValueFactory.Create(s), v);
}
return NestedArray;
else
{
throw new InvalidOperationException();
}
}

public JsonReaderInternal(JSONReader reader)
{
_reader = reader;
}

StructureImpl ResStruct = new StructureImpl();
private IValue Create() => _builder();

while ((Reader).Read())
private void AddProperty(IValue obj, string str, IValue val) => _inserter(obj, str, val);

public IValue Read<T>() where T: IEnumerable<KeyAndValueImpl>
{
if (Reader.CurrentJsonTokenType == JsonToken.PropertyName)
System.Diagnostics.Debug.Assert(typeof(T)==typeof(StructureImpl)||typeof(T)==typeof(MapImpl));
Init<T>();

try
{
string PropertyName = Reader.CurrentValue.AsString();
Reader.Read();

if (Reader.CurrentJsonTokenType == JsonToken.StartObject)
{
ResStruct.Insert(PropertyName, ReadJSONInStruct(Reader));
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartArray)
{
ResStruct.Insert(PropertyName, ReadJSONInStruct(Reader, true));
}
else
{
ResStruct.Insert(PropertyName, Reader.CurrentValue);
}
if (ReadJsonValue(out var value))
return value;
}
else if (Reader.CurrentJsonTokenType == JsonToken.EndObject)
catch (JSONReaderException)
{
break;
throw;
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartArray)
catch (Exception exc)
{
return ReadJSONInStruct(Reader, true);
throw InvalidJsonException(exc.Message);
}
}
return ResStruct;
}

public IValue ReadJSONInMap(JSONReader Reader, bool nestedArray = false)
{
throw InvalidJsonException();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потеряли исходное исключение. Вероятно, лучше добавить cause, чтобы не терять стек и описание причины.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделано намеренно, в исключении из Newtonsoft.JSON кроме позиции будет более подробное описание причины, но английское. Можно восстановить и вставить в сообщение.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А чем InnerException не подходит?

}

if (nestedArray)
private JsonToken ReadJsonToken()
{
ArrayImpl NestedArray = new ArrayImpl();
while (_reader.Read())
{
var tok = _reader.CurrentJsonTokenType;
if (tok != JsonToken.Comment)
return tok;
}

while (Reader.Read())
return JsonToken.None;
}

private bool ReadJsonValue(out IValue value)
{
switch (ReadJsonToken())
{
if (Reader.CurrentJsonTokenType == JsonToken.EndArray)
{
case JsonToken.StartObject:
var jsonObject = Create();

while (ReadJsonToken() == JsonToken.PropertyName)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если встретим комментарий, то ReadJsonToken вернет None и цикл чтения прервется. Например,

{
     "Свойство1": 1,
     // пояснение для свойства 2
    "Свойство2": "Значение"
}

После считывания 1 мы найдем комментарий, вернем Nonе и цикл закончится, не дойдя до чтения "Свойство2", нет?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нет, Nonе возвращается только когда _reader.Read() вернуло false. Комментарии не выходят за пределы ReadJsonToken()

{
var propertyName = _reader.CurrentValue.AsString();
if (!ReadJsonValue(out value))
return false;

AddProperty(jsonObject, propertyName, value);
}

if (_reader.CurrentJsonTokenType == JsonToken.EndObject)
{
value = jsonObject;
return true;
}
break;

case JsonToken.StartArray:
var resArray = new ArrayImpl();

while (ReadJsonValue(out value))
{
resArray.Add(value);
}

if (_reader.CurrentJsonTokenType == JsonToken.EndArray)
{
value = resArray;
return true;
}
break;
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartObject)
{
NestedArray.Add(ReadJSONInMap(Reader));
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartArray)
{
NestedArray.Add(ReadJSONInMap(Reader, true));
}
else
NestedArray.Add(Reader.CurrentValue);

case JsonToken.EndArray:
case JsonToken.EndObject:
case JsonToken.None:
break;

default:
value = _reader.CurrentValue;
return true;
}
return NestedArray;

value = null;
return false;
}

MapImpl ResStruct = new MapImpl();
while ((Reader).Read())
private RuntimeException InvalidJsonException(string message=null)
{
if (Reader.CurrentJsonTokenType == JsonToken.PropertyName)
{
string PropertyName = Reader.CurrentValue.AsString();
Reader.Read();

if (Reader.CurrentJsonTokenType == JsonToken.StartObject)
{
ResStruct.Insert(ValueFactory.Create(PropertyName), ReadJSONInMap(Reader));
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartArray)
{
ResStruct.Insert(ValueFactory.Create(PropertyName), ReadJSONInMap(Reader, true));
}
else
{
ResStruct.Insert(ValueFactory.Create(PropertyName), Reader.CurrentValue);
}
}
else if (Reader.CurrentJsonTokenType == JsonToken.EndObject)
{
break;
}
else if (Reader.CurrentJsonTokenType == JsonToken.StartArray)
{
return ReadJSONInMap(Reader, true);
}
var addition = string.IsNullOrWhiteSpace(message) ? string.Empty : $"\n({message})";
return new RuntimeException(string.Format(Locale.NStr
("ru='Недопустимое состояние потока чтения JSON в строке {0} позиции {1}{2}'"
+ "en='Invalid JSON reader state at line {0} position {1}{2}'"),
_reader.CurrentLine, _reader.CurrentPosition, addition));
}
return ResStruct;
}


/// <summary>
///
/// Выполняет преобразование строки, прочитанной в JSON-формате, в значение типа Дата.
Expand All @@ -197,7 +206,7 @@ public IValue ReadJSONInMap(JSONReader Reader, bool nestedArray = false)
[ContextMethod("ПрочитатьДатуJSON", "ReadJSONDate")]
public IValue ReadJSONDate(string String, IValue Format)
{

var format = Format.GetRawValue() as SelfAwareEnumValue<JSONDateFormatEnum>;
var JSONDateFormatEnum = GlobalsManager.GetEnum<JSONDateFormatEnum>();
DateFormatHandling dateFormatHandling = new DateFormatHandling();
Expand All @@ -209,14 +218,22 @@ public IValue ReadJSONDate(string String, IValue Format)
else
throw new RuntimeException(Locale.NStr("ru='Формат даты JavaScript не поддерживается.'; en='JavaScript date format is not supported'"));

string json = @"{""Date"":""" + String + @"""}";
string json = @"{""Date"":""" + String + @"""}";

var settings = new JsonSerializerSettings
{
DateFormatHandling = dateFormatHandling
};
var result = JsonConvert.DeserializeObject<ConvertedDate>(json, settings);
return ValueFactory.Create((DateTime)result.Date);
};

try
{
var result = JsonConvert.DeserializeObject<ConvertedDate>(json, settings);
return ValueFactory.Create((DateTime)result.Date);
}
catch (JsonException)
{
throw new RuntimeException(Locale.NStr("ru='Представление даты имеет неверный формат.'; en='Invalid date presentation format'"));
}
}

/// <summary>
Expand Down
Loading