Crystal JSON은 기초를 뛰어넘었습니다.
21607 단어 serializationadtjsoncrystal
소개하다.
업무 영역을 모델링할 때, 언어 원어의 맨 위에 사용자 정의 데이터 형식을 정의하는 것을 자주 발견할 수 있습니다.만약 네가 함수식 프로그래밍을 접한 적이 있다면, 너는 특별히 sum types을 쟁취하려고 노력할 것이다.
Crystal에서는 및 유형을 추상적인 유형에서 상속된 복합 유형으로 표현할 수 있습니다.덧붙인 장점으로 이러한 모델은 사용자 정의 유형 인코딩(과 디코딩)을 JSON(과 JSON에서 디코딩)으로 직접 할 수 있다. 예를 들어 official documentation과 같다.
본고에서 우리는
json
모듈과 강력한 매크로를 사용하여 크리스탈에서 JSON과 유형을 인코딩하고 디코딩하는 방법을 이해할 것이다.다음과 같은 내용을 설명합니다.
JSON::Serializable
자동 인코딩 사용사례 연구 – P2P 클라이언트
가령 우리가 대등한 응용 프로그램과 관련된 이벤트를 모델링하려고 한다면.우리는 두 가지 분야의 사건에 주목할 것이다.
Connected
이벤트: 피어와의 연결 Started
이벤트: 파일 세그먼트 다운로드를 시작합니다.Event
유형에서 계승된 유형이나 구조로 표시하는 것이다.이벤트는 본질적으로 변할 수 없기 때문에 그것들을 Getter를 가진 구조로 모델링하는 것은 의미가 있다.alias Peer = String
abstract struct Event
end
struct Connected < Event
getter peer
def initialize(@peer : Peer); end
end
struct Started < Event
getter peer, piece
def initialize(@peer : Peer, @piece : UInt32); end
end
위 부분에서Peer
은 하나의 문자열로 그의 IP 주소를 표시한다.Event
을 정의했는데 이것은 모든 구체적인 사건 유형의 기본 유형으로 정의했다.abstract 표지부호는 Event
대상을 실례화할 수 없습니다. 이것은 Event.new
이 컴파일하지 않을 것을 의미합니다.JSON 인코딩
우리가 정성스럽게 설계한 이벤트 차원 구조를 고려할 때 감사 목적을 위해 응용 프로그램이 처리하는 모든 P2P 이벤트를 영구화해야 한다는 요구가 있다.팀과의 격렬한 토론을 거쳐 우리는 JSON 형식을 채택하기로 결정했다.
Event
개의 인스턴스를 JSON으로 변환하기 위해 코드를 업데이트하겠습니다.require "json"
abstract struct Event
include JSON::Serializable
end
여기서 JSON 패키지(1행)를 가져온 다음 JSON::Serializable
모듈을 Event
(4행)에 간단하게 혼합합니다.그런가요?그래, 생각해 볼게...
e0 = Connected.new("0.0.0.0") #=> Connected(@peer="0.0.0.0")
e1 = Started.new("0.0.0.0", 2) #=> Started(@peer="0.0.0.0", @piece=2)
e0.to_json #=> {"peer":"0.0.0.0"}
e1.to_json #=> {"peer":"0.0.0.0","piece":2}
지금, 이것은 매우 인상적이다!간단하게 JSON::Serializable
모듈을 기본 유형에 포함하면 그 하위 유형에 효과적인 #to_json
방법을 배치할 수 있다.다음 작업도 효과적입니다.Connected.from_json(e0.to_json) #=> Connected(@peer="0.0.0.0")
Started.from_json(e1.to_json) #=> Started(@peer="0.0.0.0", @piece=2)
이것은 아주 좋습니다. 예를 들어 테스트 목적에 사용되지만, 컴파일할 때, 이벤트의 정확한 유형을 알 수 없기 때문에, 우리가 실제로 실행하고자 하는 것은Event.from_json(e0.to_json)
# raises "Error: can't instantiate abstract struct Event"
불행하게도, 이것은 오류를 일으킬 수 있습니다. 기본적으로 JSON::Serializable
모듈에 정의된 반서열화 프로그램은 Event
대상을 실례화하려고 시도합니다.위에서 말한 바와 같이 유형의 추상적 성질 때문에 이것은 불가능하다.그럼 우리 이제 어떡하지?구원자
JSON 부하를 올바른 실행 시 형식으로 반서열화하기 위해 이벤트 형식에 대한 추가 메타데이터를 JSON 자체에 추가합니다.우리는 이 필드를 감별기라고 부른다.
다행히도
json
모듈은 적당한 명칭의 use_json_discriminator
매크로를 가지고 있다.이것은 우리가 찾고 있는 반서열화 기능을 제공하지만, 서열화할 때 감별기 필드를 정확하게 채워야 합니다.감별기에 대한 지원을 추가하기 위해 코드를 업데이트합시다.
abstract struct Event
include JSON::Serializable
use_json_discriminator "type", {
connected: Connected,
started: Started
}
end
struct Connected < Event
getter peer
getter type = "connected"
def initialize(@peer : Peer); end
end
struct Started < Event
getter peer, piece
getter type = "started"
def initialize(@peer : Peer, @piece : UInt32); end
end
됐어, 이게 어떻게 된 거야?use_json_discriminator
을 호출합니다.이 경우, 반서열화 프로그램은 감별기가 'type' 필드에 나타나기를 원합니다.type
필드를 채워야 합니다.type
필드의 값과 감별기 맵 사이의 대응 관계를 알 수 있습니다.서열화 프로그램에 미치는 영향을 확인해 봅시다.
e0.to_json #=> {"type":"connected","peer":"0.0.0.0"}
e1.to_json #=> {"type":"started","peer":"0.0.0.0","piece":2}
유형 메타데이터는 현재 생성된 JSON의 일부입니다.이것은 반대로 다음과 같은 일을 전개할 수 있게 한다.Event.from_json(e0.to_json) #=> Connected(@type="connected", @peer="0.0.0.0")
Event.from_json(e1.to_json) #=> Started(@type="started", @peer="0.0.0.0", @piece=2)
너무 좋아요!조합 유형
상술한 방법은 필드를 기본 유형으로 하는 복합 유형에 적용되지만 만약에 우리가 다른 복합 유형을 바탕으로 복합 유형을 정의한다면?😱
Peer
의 정의를 확장해 봅시다.struct Peer
getter address : String
getter port : Int32
def initialize(@address, @port)
end
end
e0 = Connected.new(Peer.new("0.0.0.0", 8020))
e1 = Started.new(Peer.new("0.0.0.0", 8020), 2)
현재 아래의 방법은 실패했다e0.to_json
# raises "Error: no overload matches 'Peer#to_json' with type JSON::Builder"
이곳의 컴파일러는 매우 명확하다. Peer
의 대상을 어떻게 JSON으로 변환하는지 모른다.🤔 나 이거 알아!
JSON::Serializable
을 Peer
에 포함시켜 보겠습니다.struct Peer
include JSON::Serializable
getter address : String
getter port : Int32
def initialize(@address, @port)
end
end
지금 해볼게요.e0.to_json #=> {"type":"connected","peer":{"address":"0.0.0.0","port":8020}}
e1.to_json #=> {"type":"started","peer":{"address":"0.0.0.0","port":8020},"piece":2}
성공!반서열화는요?Event.from_json(s0) #=> Connected(@type="connected", @peer=Peer(@address="0.0.0.0", @port=8020))
Event.from_json(s1) #=> Started(@type="started", @peer=Peer(@address="0.0.0.0", @port=8020), @piece=2)
걸출하다그게 다야.흥미로운 기교와 이 방법의 확장성에 대한 더 많은 고려를 정리해 봅시다.이벤트 하위 유형 추가
현재 기본 이벤트 유형과 그 실현은 모두 JSON을 지원한다. 이것은 이 두 가지 유형의 코드가 모두 JSON 지원과 관련된 위치를 포함한다는 것을 의미한다.
그 자체는 문제가 되지 않지만 JSON 논리가 세부 사항을
Event
서브 유형 정의에 누설하고 있는 것 같다.Event
실현자가 유형 필드를 알 필요가 없도록 할 수 있습니까?결국, type
Getter는 프로그래밍을 통해 계산할 수 있는 값을 되돌려줍니다. 이 예에서 형식 이름의downcase 버전입니다.사실은 우리가 할 수 있는 일이 다음과 같다는 것을 증명한다.
abstract struct Event
include JSON::Serializable
use_json_discriminator "type", {connected: Connected, started: Started}
macro inherited
getter type : String = {{@type.stringify.downcase}}
end
end
잠깐만, 6호선의 macro inherited
은 무엇에 관한 것입니까?그것은 주체의 코드를 Event
에서 계승하는 모든 유형에 주입하는 특수한 매크로 갈고리이다.이것이 바로 우리가 필요로 하는 것이다. 왜냐하면 type
Getter를 Event
의 모든 실현에 주입하고,stringized와downcased 형식 이름으로 설정할 수 있기 때문이다.7행에서는 매크로에 나타나는 @type
이 인스턴스화된 유형의 이름으로 해석됩니다.다음 코드는 다음과 같습니다.
struct Connected < Event
getter peer
def initialize(@peer : Peer); end
end
struct Started < Event
getter peer, piece
def initialize(@peer : Peer, @piece : UInt32); end
end
JSON 논리의 흔적이 없어요.🎉새로운
Event
유형을 소개하여 이 점을 보여 드리겠습니다.# An event indicating the completion of a file piece.
struct Completed < Event
getter peer, piece
def initialize(@peer : Peer, @piece : UInt32); end
end
예상한 바와 같이 실현자에게는 별도의 논리가 없지만, 우리는 여전히 감별기 맵을 업데이트해야 한다.abstract struct Event
use_json_discriminator "type", {
connected: Connected,
started: Started,
completed: Completed
}
# ...
end
도전하다.수동으로 맵을 업데이트하는 것을 피할 수 있습니까?팁: 다음 매크로는 우리가 찾는
NamedTupleLiteral
을 생성하는 데 적합합니다.abstract struct Event
macro subclasses
{
{% for name in @type.subclasses %}
{{ name.stringify.downcase.id }}: {{ name.id }},
{% end %}
}
end
end
Event.subclasses # => {connected: Connected, started: Started, completed: Completed}
불행히도 다음과 같은 방법은 효과가 없다.use_json_discriminator "type", Event.subclasses
#=> raises Error: mapping argument must be a HashLiteral or a NamedTupleLiteral, not Call
이는 매크로 use_json_discriminator
이 펼쳐졌을 때 Event.subclasses
이 아직 펴지지 않았기 때문이다.나는 매크로를 사용할 때 자주 이런 문제가 발생하는 것을 본 적이 있다. 매크로는 대량의 코드를 작성하는 것을 피할 수 있지만, 매크로를 작성하는 것은 사람을 낙담하게 하고 복잡하게 할 수도 있다.다음은 나의 건의이다.
when working with macros, keep it simple. If something feels too complicated, it probably is.
어쨌든 해커 솔루션을 공유하고 싶다면 아래 부분에 댓글을 남겨주세요.
한층 더 읽다
json
모듈 설명서 here만약 당신이 연락을 유지하고 싶다면, 당신은 구독하거나 나를 주목할 수 있습니다.너는 때때로 Twitch에서 나의 실시간 인코딩을 찾을 수 있다📺
Reference
이 문제에 관하여(Crystal JSON은 기초를 뛰어넘었습니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/lbarasti/crystal-json-beyond-the-basics-5d45텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)