2009-04-08 2 views
9

Я рассматриваю перенос очень простой библиотеки текстовых шаблонов для scala, в основном как упражнение в изучении языка. Библиотека в настоящее время реализуется как в Python и JavaScript, и его основные операции более или менее сводится к этому (в Python):Как указать тип неструктурированных данных JSON в Scala?

template = CompiledTemplate('Text {spam} blah {eggs[1]}') 
data = { 'spam': 1, 'eggs': [ 'first', 'second', { 'key': 'value' }, true ] } 
output = template.render(data) 

Все это ужасно трудно сделать в Scala, но что я» m неясно о том, как наилучшим образом выразить статический тип параметра data.

В основном, этот параметр должен содержать типы вещей, которые вы найдете в JSON: несколько примитивов (строки, ints, boolean, null) или списки с нулевым или большим количеством элементов или карты с нулем или более Предметы. (Для целей этого вопроса карты могут быть ограничены наличием строковых ключей, что похоже на то, как Scala все равно любит.)

Моя первоначальная мысль состояла в том, чтобы использовать Map[string, Any] как объект верхнего уровня, но это не кажется мне совершенно правильным. На самом деле я не хочу добавлять в него произвольные объекты любого класса; Я хочу только элементы, описанные выше. В то же время, я думаю, что в Java ближе всего я бы смог получить Map<String, ?>, и я знаю, что один из авторов Scala разработал дженерики Java.

Мне особенно любопытно, как другие функциональные языки с похожими типами систем справляются с такой проблемой. У меня такое ощущение, что то, что я действительно хочу здесь сделать, придумал набор классов case, с которыми я могу сопоставлять шаблоны, но я не совсем в состоянии представить, как это будет выглядеть.

У меня есть Программирование в Scala, но если честно, мои глаза начали остекление над немного в ковариационной/контравариации вещи и я надеюсь, что кто-нибудь может объяснить мне это немного более четко и лаконично.

ответ

14

Вы находитесь на том, что хотите, чтобы какие-то классы классов моделировали ваши типы данных. В функциональных языках такие вещи называются «абстрактными типами данных», и вы можете прочитать все о том, как Haskell использует их с помощью Googling. Scala эквивалент ADTs Haskell использует закрытые черты и классы case.

Давайте рассмотрим rewrite of the JSON parser combinator из стандартной библиотеки Scala или книги Programming in Scala. Вместо использования Map [String, Any] для представления объектов JSON и вместо использования Any для представления произвольных значений JSON используется абстрактный тип данных JsValue для представления значений JSON. JsValue имеет несколько подтипов, представляющих возможные виды значений JSON: JsString, JsNumber, JsObject, JsArray, JsBoolean (JsTrue, JsFalse) и JsNull.

Манипулирование данными JSON этой формы включает в себя сопоставление образцов. Поскольку JsValue запечатан, компилятор предупредит вас, если вы не рассматривали все случаи. Например, код toJson, метод, который принимает JsValue и возвращает String представление того, что ценности, выглядит следующим образом: соответствие

def toJson(x: JsValue): String = x match { 
    case JsNull => "null" 
    case JsBoolean(b) => b.toString 
    case JsString(s) => "\"" + s + "\"" 
    case JsNumber(n) => n.toString 
    case JsArray(xs) => xs.map(toJson).mkString("[",", ","]") 
    case JsObject(m) => m.map{case (key, value) => toJson(key) + " : " + toJson(value)}.mkString("{",", ","}") 
    } 

Pattern и позволяет нам убедиться, что мы имеем дело с в каждом случае, и также «разворачивает» базовое значение из своего JsType. Он обеспечивает безопасный способ узнать, что мы справляемся с каждым случаем.

Кроме того, если вы знаете во время компиляции структуру данных JSON, с которыми имеете дело, вы можете сделать что-то действительно крутое, как n8han's extractors. Очень мощный материал, проверьте это.

+0

Спасибо, это было именно то, что я искал. Каков источник переписанного парсера JSON, с которым вы связаны, на пастебине? (Я заметил, что встроенный парсер в библиотеках Scala использует Map [String, Any].) –

+0

Я написал парсер, связанный с пастебином. Я имел в виду сделать из него полноценный проект, но не нашел времени. –

1

Ну, есть пара способов приблизиться к этому. Я бы, вероятно, просто использовал Map[String, Any], который должен работать просто отлично для ваших целей (при условии, что карта от collection.immutable, а не collection.mutable). Тем не менее, если вы действительно хотите, чтобы пройти через какую-то боль, то можно указать тип этого:

sealed trait InnerData[+A] { 
    val value: A 
} 

case class InnerString(value: String) extends InnerData[String] 
case class InnerMap[A, +B](value: Map[A, B]) extends InnerData[Map[A, B]] 
case class InnerBoolean(value: Boolean) extends InnerData[Boolean] 

Теперь, при условии, что вы читаете поле JSON data в поле Scala по имени jsData, вы дам этому полю следующего типа:

val jsData: Map[String, Either[Int, InnerData[_]] 

Каждый раз, когда вы тянете поле из jsData, вам нужно будет сопоставление с образцом, проверяя, было ли значение типа Left[Int] или Right[InnerData[_]] (два подтипов Either[Int, InnerData[_]]). После того, как у вас есть внутренние данные, вы затем сопоставляете совпадение по с тем, что определяет, представляет ли он InnerString, InnerMap или InnerBoolean.

Технически вы должны выполнить подобный подход, чтобы использовать данные, как только вы вытащите его из JSON. Преимуществом хорошо типизированного подхода является то, что компилятор проверит вас, чтобы вы не упустили какие-либо возможности. Недостаток заключается в том, что вы не можете просто пропустить невозможность (например, 'eggs' сопоставление с Int). Кроме того, есть некоторые накладные расходы, налагаемые всеми этими объектами-оболочками, поэтому следите за этим.

Обратите внимание, что Scala действительно позволяет вам определить тип псевдонима, который должен сократить на сумму ЛСС, необходимых для этого:

type DataType[A] = Map[String, Either[Int, InnerData[A]]] 

val jsData: DataType[_] 

Добавьте несколько неявные преобразования, чтобы сделать API довольно, и вы должны быть все красиво и денди.

+0

Можете ли вы немного уточнить этот тип [Int, InnerData [A]]? Я не понимаю, почему это Int, во-первых, поскольку примитивы, которые я хочу, являются из набора (Int, String, Boolean, null). Благодаря! –

+0

Либо в Java :-) http://www.ibm.com/developerworks/java/library/j-ft13/index.html – ZiglioUK

1

JSON используется в качестве примера в «Программирование в Scala» в главе по разборчивости комбинаторов.

+0

Я видел этот раздел, но результирующие типы данных - это своего рода дерево разбора, которое происходит из разбор строки JSON, и я не обязательно буду иметь буквальную строку JSON для синтаксического анализа; код, который вызывает 'render()', может иметь произвольные данные, которые он собирается из другого источника. –