Страницы

Поиск по вопросам

суббота, 15 июня 2019 г.

Десериализация json-строки в Словарь (Dictionary)


{ "rates_scores_stats": [{ "name": 10, "value": 3545 }, { "name": 9, "value": 1004 }, { "name": 8, "value": 820 }, { "name": 7, "value": 493 }, { "name": 6, "value": 218 }, { "name": 5, "value": 138 }, { "name": 4, "value": 80 }, { "name": 3, "value": 41 }, { "name": 2, "value": 26 }, { "name": 1, "value": 83 } ], "rates_statuses_stats": [{ "name": "Запланировано", "value": 2506 }, { "name": "Смотрю", "value": 7861 }, { "name": "Просмотрено", "value": 1947 }, { "name": "Отложено", "value": 1443 }, { "name": "Брошено", "value": 1358 } ] }
Есть JSON описанный выше. Для десериализации я использую Newtonsoft.Json. В частности, метод JsonConvert.DeserializeObject(json)
Я знаю, что для этого json можно описать типичный класс следующим образом

Тоже самое текстом: http://pastexen.com/code.php?file=aMvORa6As3.cs
Но мне надо превратить это не в коллекцию вспомогательных классов или же коллекцию KeyValuePair. Мне нужен именно словарь, чтобы я мог обратиться к такому объекту следующим образом:
var result = JsonConvert.DeserializeObject(json); var val = result.rates_scores_stats[10]; // val = 3545 var val1 = result.rates_statuses_stats["Смотрю"]; // val1 = 7861
Можно ли как-нибудь сериализовать это в словарь? Может с помощью get/set как-то извернуться?
P.S.: Я знаю, что в шарпе есть Linq, который позволит сделать что-то типо result.FirstOrDefault(x.value => x.name == "10"), но я пишу переносимую библиотеку и хочу упростить доступ к данным =/


Ответ

Например, можно десериализовать в JObject, и потом сконвертировать данные вручную:
var obj = JObject.Parse(json); var rates_scores_stats = ((JArray)obj["rates_scores_stats"]).ToDictionary(entry => (int)entry["name"], entry => (int)entry["value"]); var rates_statuses_stats = ((JArray)obj["rates_statuses_stats"]).ToDictionary(entry => (string)entry["name"], entry => (int)entry["value"]); var result = new MyClass() { rates_scores_stats = rates_scores_stats, rates_statuses_stats = rates_statuses_stats };

Альтернативный ответ здесь. Немного модифицировав его, можно получить менее «ручное» решение.
Создадим отдельный конвертер (спёрт и модифицирован из ответа по ссылке):
public class JsonGenericDictionaryOrArrayConverter : JsonConverter { // поскольку у вас в JSON'е не key/value, а name/value, нужен специальный класс // для десериализации одного элемента массива class NameValuePair { public N name { get; set; } public V value { get; set; } }
public override bool CanConvert(Type objectType) { return GetDictionaryKeyValueTypes(objectType).Count() == 1; }
public override bool CanWrite { get { return false; } }
object ReadJsonGeneric( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var tokenType = reader.TokenType;
var dict = existingValue as IDictionary; if (dict == null) { var contract = serializer.ContractResolver.ResolveContract(objectType); dict = (IDictionary)contract.DefaultCreator(); }
if (tokenType == JsonToken.StartArray) { var pairs = new JsonSerializer() .Deserialize[]>(reader); if (pairs == null) return existingValue; foreach (var pair in pairs) dict.Add(pair.name, pair.value); } else if (tokenType == JsonToken.StartObject) { // Using "Populate()" avoids infinite recursion. // https://github.com/JamesNK/Newtonsoft.Json/blob/ // ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Converters/ // CustomCreationConverter.cs serializer.Populate(reader, dict); } return dict; }
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Throws an exception if not exactly one. var keyValueTypes = GetDictionaryKeyValueTypes(objectType).Single();
var method = GetType().GetMethod( "ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); var genericMethod = method.MakeGenericMethod( new[] { keyValueTypes.Key, keyValueTypes.Value }); return genericMethod.Invoke( this, new object[] { reader, objectType, existingValue, serializer }); }
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer) { throw new NotSupportedException(); }
static IEnumerable> GetDictionaryKeyValueTypes(Type type) { foreach (Type intType in GetInterfacesAndSelf(type)) { if (intType.IsGenericType && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { var args = intType.GetGenericArguments(); if (args.Length == 2) yield return new KeyValuePair(args[0], args[1]); } } }
static IEnumerable GetInterfacesAndSelf(Type type) { if (type == null) throw new ArgumentNullException(); if (type.IsInterface) return new[] { type }.Concat(type.GetInterfaces()); else return type.GetInterfaces(); } }
Имея этот вспомогательный класс, можно десериализовать просто так:
var settings = new JsonSerializerSettings { Converters = { new JsonGenericDictionaryOrArrayConverter() } }; var result = JsonConvert.DeserializeObject(jsonString, settings);

Обновление: Для portable library (.NET 4.5.1 + Windows Universal 8.1 + Windows Phone 8.1) у меня скомпилировалось такое:
public class JsonGenericDictionaryOrArrayConverter : JsonConverter { // поскольку у вас в JSON'е не key/value, а name/value, нужен специальный класс // для десериализации одного элемента массива class NameValuePair { public N name { get; set; } public V value { get; set; } }
public override bool CanConvert(Type objectType) { return GetDictionaryKeyValueTypes(objectType).Count() == 1; }
public override bool CanWrite { get { return false; } }
public object ReadJsonGeneric( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var tokenType = reader.TokenType;
var dict = existingValue as IDictionary; if (dict == null) { var contract = serializer.ContractResolver.ResolveContract(objectType); dict = (IDictionary)contract.DefaultCreator(); }
if (tokenType == JsonToken.StartArray) { var pairs = new JsonSerializer() .Deserialize[]>(reader); if (pairs == null) return existingValue; foreach (var pair in pairs) dict.Add(pair.name, pair.value); } else if (tokenType == JsonToken.StartObject) { // Using "Populate()" avoids infinite recursion. // https://github.com/JamesNK/Newtonsoft.Json/blob/ // ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Converters/ // CustomCreationConverter.cs serializer.Populate(reader, dict); } return dict; }
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Throws an exception if not exactly one. var keyValueTypes = GetDictionaryKeyValueTypes(objectType).Single();
var method = GetType().GetTypeInfo().GetDeclaredMethod("ReadJsonGeneric"); var genericMethod = method.MakeGenericMethod( new[] { keyValueTypes.Key, keyValueTypes.Value }); return genericMethod.Invoke( this, new object[] { reader, objectType, existingValue, serializer }); }
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer) { throw new NotSupportedException(); }
static IEnumerable> GetDictionaryKeyValueTypes(Type type) { foreach (Type intType in GetInterfacesAndSelf(type)) { if (intType.GetTypeInfo().IsGenericType && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { var args = intType.GetTypeInfo().GenericTypeArguments; // вроде бы именно GenericTypeArguments, а не Parameters if (args.Length == 2) yield return new KeyValuePair(args[0], args[1]); } } }
static IEnumerable GetInterfacesAndSelf(Type type) { if (type == null) throw new ArgumentNullException(); if (type.GetTypeInfo().IsInterface) return new[] { type }.Concat(type.GetTypeInfo().ImplementedInterfaces); else return type.GetTypeInfo().ImplementedInterfaces; } }

Не понятен смысл DI

Мне не до конца понятен смысл использование DI (например autofac). Возможно, я не правильно трактую его смысл из-за неверной области применения.
Контекст задачи следующий: необходимо внедрить autofac модуль в систему тестирования. Собственно проблема, зачем мне писать код с разрешением зависимостей через тот же autofac если могу явно создавать объекты и передовать их в конструктор.
К примеру, есть ApiController, у него в конструкторе куча аргументов типа пользовательского контекста, менеджера регистрации, менеджера для отправки смс и т.д. Все эти объекты в свою очередь тоже достаточно сложно инициализируются. К примеру:
Вариант с Autofac
ISomeClassInterface someClass; var builder = new ContainerBuilder(); builder.RegisterType(); var container = builder.Build(); someClass = new SomeClass(container.Resolve();
Вариант без Autofac'a
ISomeClassInterface someClass; ISomeService service = new SomeService(some arguments...); someClass = new SomeClass(service);


Ответ

Вы сами ответили на свой вопрос: "Все эти объекты в свою очередь тоже достаточно сложно инициализируются". Чем сложнее, тем проще ошибиться. Безусловно, можно не использовать никакой DI. Но, во-первых, DI упрощает инициализацию объектов. Во-вторых, DI упрощает подмену объектов. Например, вы захотите использовать другой менеджер регистрации. Без DI придется менять код в нескольких местах, с DI - в одном.
Сравните:
A a = new A(new B(new C()), new D(new E())...);
И как-то условно
A a = container.Resolve();

Тест не проходит

Есть вспомогательный класс DialogIdlingResource, благодаря которому пытаюсь запустить свои тесты, все тесты проходят за исключением этого запускается активити и доходит до шага нажатия на кнопку:
AcceptanceHelper.clickOnButtonInLayout(R.id.mainSignButton, R.string.common_signin_button_text, R.id.inputLayout)
, после чего всё замирает, хотя тест по идее должен отрабатывать(а именно проверять текст в методе isDialogRunning):
@Test fun signInUserWithInvalidEmail() { goToSignIn() AcceptanceHelper.updateValidationTextView(R.string.ui_data_attribute_email, "kokojambo@mail.ru") AcceptanceHelper.updateValidationTextView(R.string.ui_data_attribute_password, VALID_PASSWORD) AcceptanceHelper.clickOnButtonInLayout(R.id.mainSignButton, R.string.common_signin_button_text, R.id.inputLayout) val idlingResource = DialogIdlingResource() registerDialogIdlingResource() unregisterDialogIdlingResource() } private fun registerDialogIdlingResource() { val instrumentation = InstrumentationRegistry.getInstrumentation() idlingResource = DialogIdlingResource() Espresso.registerIdlingResources(idlingResource) }
private fun unregisterDialogIdlingResource() { Espresso.unregisterIdlingResources(idlingResource) }
Предпологаю что ошибка из за двух методов регистрации и UNрегистрации idlingResource
Но по сути всё должно работать, но может быть где то, что скорей всего сделал ошибку, код вспомогательного класса:
class DialogIdlingResource(private val waitTimeSeconds: Int = 5) : IdlingResource { private var resourceCallback: IdlingResource.ResourceCallback? = null private var startTime = -1L
override fun getName(): String { return DialogIdlingResource::class.java.name }
override fun isIdleNow(): Boolean { if (startTime < 0) { startTime = System.currentTimeMillis() } val timeOut = System.currentTimeMillis() - waitTimeSeconds * 1000 > startTime if (timeOut) throw TimeoutException("error") val idle = !isDialogRunning if (idle && resourceCallback != null) { resourceCallback!!.onTransitionToIdle() } return idle }
override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback) { this.resourceCallback = resourceCallback }
private val isDialogRunning: Boolean get() { try { onView(Matchers.allOf(withId(R.id.titleTextView), ViewMatchers.withText("Warning"))) .check(ViewAssertions.matches(isDisplayed())) } catch (e: NoMatchingViewException) { e.printStackTrace() System.out.println("some text") } return true }
}


Ответ

Сразу скажу у Вас слишком замудренная реализация того, что можно сделать гораздо проще, ну как мне кажется. На данный момент я вижу два варианта:
Первый вариант подробно описан по ссылке в данной репозитории github. Принцип которого заключается в проставлении необходимых Вам параметров и место где они распологаются. Также советую обратить внимание на флаги параметра visibility.
Ну а второй вариант будет выглядеть примерно следующим образом. Создаём класс, который унаследуем от IdlingResource, имплементируем его методы и заполняем:
class ElapsedTimeIdlingResource(private val activity: SignActivity?) : IdlingResource { private var callback: IdlingResource.ResourceCallback? = null
override fun getName(): String { return "SignInScreenTest" }
override fun isIdleNow(): Boolean { val idle = isIdle if (idle) callback!!.onTransitionToIdle() return idle }
val isIdle: Boolean get() = activity != null && callback != null
override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback) { this.callback = resourceCallback } }
После этого в Ваших тестах после нажатия на кнопку реализуем вызов следующим образом:
val activity = mActivityTestRule.getActivity() val idlingResource = ElapsedTimeIdlingResource(activity) Espresso.registerIdlingResources(idlingResource)
Где мы "получаем" компоненты нашей аквтивности, иначе открытые элементы/переходы возможно приведут к null вашей активити. Как я понял, Вы пытыетесь открыть в Вашей activity диалог. После чего регистируем наши idlingResource из класса. После того как открылся Ваш диалог например, Вы можете проверить элементы содержащиеся в нём, или просто вернуться назад. После всех операций, в этом же тесте нужно вызвать:
Espresso.unregisterIdlingResources(idlingResource)
Также Вы наверное видели уже что возможно поместить методы registerIdlingResource/unregisterIdlingResources в аннотации @Before/@After чтобы не прописывать всегда одно и то же, если тестов скажем несколько.
И опять же для дополнительного чтения : пример использования

Entity FrameWork Code First атрибут альтернативного ключа

Доброго времени суток, хочу в бд для полей задать составной альтернативный ключ, как это можно провернуть через атрибуты Entity Framework? Есть еще мысль попробовать через миграцию, но предпочтителен через атрибуты, поиск не дал наличия такого атрибута, может плохо искал?
ps. EF6, .Net, C#, code first


Ответ

В документации от Microsoft сказано:
Alternate keys can not be configured using Data Annotations.
Перевод от меня:
Альтернативные ключи не могут быть настроены с помощью аннотации данных, т.е. атрибутов.
Поскольку Вы используете Code-First, то рекомендую попробовать Fluent API.
Приведу пример по настройке одного свойства с помощью Fluent API^
class MyContext : DbContext { public DbSet Cars { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .HasAlternateKey(c => c.LicensePlate); } }
class Car { public int CarId { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } }
Полный список атрибутов поддерживаемых EF:
KeyAttribute StringLengthAttribute MaxLengthAttribute ConcurrencyCheckAttribute RequiredAttribute TimestampAttribute ComplexTypeAttribute ColumnAttribute TableAttribute InversePropertyAttribute ForeignKeyAttribute DatabaseGeneratedAttribute NotMappedAttribute

Дождаться завершения потока

Потоки создаются в цикле, и их количество заранее не известно
threads.push_back(boost::thread(readFile, it->path()));
Как узнать, когда все такие потоки завершили работу?


Ответ

Например,
for (auto& t : threads) t.join();
?

Как сделать приложения на latex с кириллической нумерацией по центру?

Требуется сделать в latex-документе приложения такого вида:
Буксую уже на стадии кириллической нумерации:
\makeatletter
enewcommand\appendix{\par \setcounter{section}{0}% \setcounter{subsection}{0}% \gdef\thesection{\@Asbuk\c@section}} \makeatother
не даёт никакого результата и постоянно выводит pdfTeX warning (ext4): destination with the same identifier (name{section.1}) has been already used, duplicate ignored. Использование вместо \@Asbuk других наборов символов работает (правда, формируются неправильные ссылки).
Документ имеет класс extarticle
Может, есть какие-то готовые рецепты? Если нет, то подскажите мне, пожалуйста, что нужно сделать.


Ответ

изменить нумерацию разделов/секций (sections) можно, переопределив команду \thesection

enewcommand{\thesection}{\Asbuk{section}} изменить заголовок раздела/секции (section) можно, например, с помощью пакета titlesec, используя входящую в него команду \titleformat
так как вам нужно переопределять заголовки приложений (appendices), то, соответственно, обе этих команды нужно вставить уже после команды \appendix
наглядная демонстрация. код:
\documentclass{extarticle} \usepackage[T2A]{fontenc} \usepackage[utf8]{inputenc} \usepackage[russian]{babel} \usepackage{titlesec} \title{пример с приложениями} \begin{document} \section{раз} текст \section{два} текст \appendix \titleformat{\section}[display] {
ormalfont\Large\bfseries} {\centering Приложение\ \thesection\\(справочное)} {0pt}{\Large\centering}
enewcommand{\thesection}{\Asbuk{section}}
\section{приложение раз} текст \section{приложение два} текст \end{document}
картинкой:

Запустить скрипт только один раз?

В общем ситуация такая. Есть скрипт, который может работать в некоторых редких случаях больше чем интервал в cronjob, но запускать его при этом нужно каждую минуту, так как в 95% случаев он отрабатывает за несколько секунд. Нужно исключить возможность повторного запуска. Сразу оговорюсь, это бесплатный хостинг, поэтому SSH доступа нет, и мониторить процессы не получается. Остаётся лишь реализовать это средствами PHP
Пробовал так
//Статический файл который будет хранить имя временного файла $lockFilename = __DIR__ . '/lock.dat';
//Проверяем существует ли временный файл if (file_exists($lockFilename) && ($lock = file_get_contents($lockFilename)) && file_exists($lock)) { echo "Cron running now {$lock}
"; exit(); }
//Файл должен умереть при закрытии дескриптора $tmpHandle = tmpfile();
//Получаем имя временного файла $metaData = stream_get_meta_data($tmpHandle);
//Записываем имя временного файла в статический файл file_put_contents($lockFilename, $metaData['uri']);
//Cron симуляция echo "Cron started
";
sleep(20);
echo "Cron stopped
"; exit();
Но файл остаётся, если прервать скрипт через Ctrl+C
Думал решить задачу через memcache и время жизни ключа, но обратно таки бесплатный хостинг не предоставляет memcache в распоряжение


Ответ

Вариант с блокировкой файла:
$fp = fopen("lock", "w+");
if (flock($fp, LOCK_EX | LOCK_NB)) { // выполняем блокировку
/* * Тут код который нужно выполнить */
flock($fp, LOCK_UN); // отпираем файл } else { echo "Не удалось получить блокировку, файл уже заблокирован!"; }
fclose($fp); unlink("lock");

Вариант с обработкой сигналов:
declare(ticks = 1); // включить обработку сигнала
function sigint() {
exit();
} pcntl_signal(SIGINT, 'sigint'); pcntl_signal(SIGTERM, 'sigint');
/* * Тут код который нужно выполнить */